From 2fd025951c63aa764612ac48d36604077e02e508 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Thu, 25 Jun 2026 12:41:04 +0300 Subject: [PATCH 1/2] Treat empty env vars as unset in config expansion --- CHANGELOG.md | 4 ++++ haiku_rag_slim/haiku/rag/config/loader.py | 6 +++--- tests/test_config.py | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc9932c..7ce56292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Changed + +- A bare `${VAR}` in YAML config now raises `MissingEnvVarError` when the variable is set but empty, matching the unset case. Use `${VAR:-default}` to allow an empty/absent value. + ## [0.61.2] - 2026-06-24 ### Fixed diff --git a/haiku_rag_slim/haiku/rag/config/loader.py b/haiku_rag_slim/haiku/rag/config/loader.py index abc9892b..883a6507 100644 --- a/haiku_rag_slim/haiku/rag/config/loader.py +++ b/haiku_rag_slim/haiku/rag/config/loader.py @@ -12,7 +12,7 @@ _ENV_VAR_PATTERN = re.compile(r"\$\$|\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\ class MissingEnvVarError(ValueError): - """A ${VAR} in the config references an unset environment variable.""" + """A ${VAR} in the config references an unset or empty environment variable.""" def _expand_str(value: str) -> str: @@ -20,12 +20,12 @@ def _expand_str(value: str) -> str: if match.group(0) == "$$": return "$" name, default = match.group(1), match.group(2) - if name in os.environ and (default is None or os.environ[name] != ""): + if os.environ.get(name, "") != "": return os.environ[name] if default is not None: return default raise MissingEnvVarError( - f"Config references unset environment variable ${{{name}}}. " + f"Config references unset or empty environment variable ${{{name}}}. " f"Set it, or use ${{{name}:-default}} to provide a fallback." ) diff --git a/tests/test_config.py b/tests/test_config.py index fac05ad0..ba659d29 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -419,6 +419,18 @@ def test_expand_env_var_unset_raises(tmp_path, monkeypatch): load_yaml_config(config_file) +def test_expand_env_var_empty_raises(tmp_path, monkeypatch): + """A bare ${VAR} set to an empty string is treated as unset and raises.""" + from haiku.rag.config.loader import MissingEnvVarError + + monkeypatch.setenv("HAIKU_TEST_EMPTY", "") + config_file = tmp_path / "test.yaml" + config_file.write_text("api_key: ${HAIKU_TEST_EMPTY}") + + with pytest.raises(MissingEnvVarError, match="HAIKU_TEST_EMPTY"): + load_yaml_config(config_file) + + def test_expand_env_var_default_when_unset(tmp_path, monkeypatch): """${VAR:-default} falls back to the default when VAR is unset.""" monkeypatch.delenv("HAIKU_TEST_MISSING", raising=False) From b61134b747815da1438741f6aa493228743e910a Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Thu, 25 Jun 2026 13:00:29 +0300 Subject: [PATCH 2/2] Log FTS index build failures at WARNING --- CHANGELOG.md | 4 +++ .../haiku/rag/store/repositories/chunk.py | 3 +- tests/test_chunk.py | 31 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ce56292..87b76ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Fixed + +- A failed FTS index build is logged at `WARNING` instead of `DEBUG`, so silent full-text search degradation is visible. + ### Changed - A bare `${VAR}` in YAML config now raises `MissingEnvVarError` when the variable is set but empty, matching the unset case. Use `${VAR:-default}` to allow an empty/absent value. diff --git a/haiku_rag_slim/haiku/rag/store/repositories/chunk.py b/haiku_rag_slim/haiku/rag/store/repositories/chunk.py index 09a0f40a..ad319121 100644 --- a/haiku_rag_slim/haiku/rag/store/repositories/chunk.py +++ b/haiku_rag_slim/haiku/rag/store/repositories/chunk.py @@ -33,8 +33,7 @@ class ChunkRepository: replace=True, ) except Exception as e: - # Log the error but don't fail - FTS might already exist - logger.debug(f"FTS index creation skipped: {e}") + logger.warning(f"FTS index build failed; full-text search degraded: {e}") def _contextualize_content(self, chunk: Chunk) -> str: """Generate contextualized content for FTS by prepending headings.""" diff --git a/tests/test_chunk.py b/tests/test_chunk.py index 2dcc7299..c3efccc0 100644 --- a/tests/test_chunk.py +++ b/tests/test_chunk.py @@ -510,3 +510,34 @@ async def test_chunk_content_fts_without_headings(temp_db_path): # Both should be the same when no headings assert record["content"] == "Plain content without headings." assert record["content_fts"] == "Plain content without headings." + + +async def test_ensure_fts_index_warns_on_failure(temp_db_path): + """A failed FTS index build is surfaced at WARNING, not swallowed silently.""" + import logging + + from haiku.rag.store.repositories import chunk as chunk_module + + async with HaikuRAG(db_path=temp_db_path, config=Config, create=True) as client: + repo = client.chunk_repository + + async def _boom(*_args, **_kwargs): + raise RuntimeError("index build failed") + + repo.store.chunks_table.create_index = _boom + + records: list[logging.LogRecord] = [] + + class _Capture(logging.Handler): + def emit(self, record: logging.LogRecord) -> None: + records.append(record) + + handler = _Capture(level=logging.WARNING) + chunk_module.logger.addHandler(handler) + try: + await repo._ensure_fts_index() + finally: + chunk_module.logger.removeHandler(handler) + + assert [r for r in records if r.levelno == logging.WARNING] + assert any("index build failed" in r.getMessage() for r in records)