-
Notifications
You must be signed in to change notification settings - Fork 2
cowork: scan fails loudly when no config files load (silent-green guard) #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,8 @@ def require_license(product: str) -> None: # type: ignore[misc] | |
| invoke_without_command=True, | ||
| ) | ||
| console = Console() | ||
| # Warnings that must not pollute machine-readable (JSON) stdout go to stderr. | ||
| _warn_console = Console(stderr=True) | ||
|
|
||
| _require_license_strict: bool = False | ||
|
|
||
|
|
@@ -280,22 +282,54 @@ def scan( | |
| raise typer.Exit(code=1) | ||
|
|
||
| env_configs: dict[str, dict[str, Any]] = {} | ||
| files_loaded = 0 | ||
| for env_name, dir_path in dir_mapping.items(): | ||
| env_configs[env_name] = {} | ||
| key_sources: dict[str, str] = {} # flattened key -> file that provided it | ||
| p = Path(dir_path) | ||
| if not p.is_dir(): | ||
| console.print( | ||
| f"[yellow]Warning: '{dir_path}' is not a directory, skipping.[/yellow]" | ||
| ) | ||
| continue | ||
| # Load all supported config files in the directory and merge | ||
| # Load all supported config files in the directory and merge. | ||
| # Later files silently overwrite earlier ones via dict.update(); surface | ||
| # conflicting duplicate keys instead of letting glob order pick a winner. | ||
| for ext in ("*.yaml", "*.yml", "*.json", "*.toml", "*.env"): | ||
| for f in p.glob(ext): | ||
| for f in sorted(p.glob(ext)): | ||
| try: | ||
| data = load_file(str(f)) | ||
| env_configs[env_name].update(data) | ||
| except Exception as e: | ||
| console.print(f"[yellow]Warning: could not load {f}: {e}[/yellow]") | ||
| continue | ||
| for k, v in data.items(): | ||
| prev_file = key_sources.get(k) | ||
| if prev_file is not None and env_configs[env_name].get(k) != v: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When duplicate definitions use values such as YAML Useful? React with 👍 / 👎. |
||
| _warn_console.print( | ||
| f"[yellow]Warning: key '{k}' is defined with conflicting " | ||
| f"values in {prev_file} and {f} (environment " | ||
| f"'{env_name}'); using the value from {f} " | ||
| "(alphabetical filename order).[/yellow]" | ||
| ) | ||
| key_sources[k] = str(f) | ||
| env_configs[env_name].update(data) | ||
| files_loaded += 1 | ||
|
|
||
| # Silent-failure guard: if nothing was actually loaded, any "no drift" | ||
| # result would be a false green. Fail loudly instead. | ||
| if files_loaded == 0: | ||
| console.print( | ||
| "[red]ERROR: No config files could be loaded from any environment " | ||
| "directory. Refusing to report 'no drift' from an empty scan.[/red]" | ||
| ) | ||
| raise typer.Exit(code=1) | ||
| if not env_configs.get(baseline): | ||
| console.print( | ||
| f"[red]ERROR: Baseline environment '{baseline}' loaded no config " | ||
| "keys; comparison against an empty baseline would flag every key " | ||
| "as drift.[/red]" | ||
| ) | ||
| raise typer.Exit(code=1) | ||
|
|
||
| results = diff_environments(env_configs, baseline_env=baseline) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The outer extension loop means files are alphabetical only within each format, not across the directory as the warning claims. For example,
z.yamlis processed beforea.json, soa.jsonwins even thoughz.yamlis alphabetically last; mixed-format environments can therefore resolve duplicate keys to an unexpected value. Gather all supported paths and sort the combined list before loading them.Useful? React with 👍 / 👎.