From 0da9e24199e0c81acadf870c8032e6368868eb6d Mon Sep 17 00:00:00 2001 From: elhoim Date: Sun, 30 Aug 2026 23:36:26 +0000 Subject: [PATCH] Fix silent key drop and EOFError in configure_module --set/non-interactive configure_module() only checked declared config_keys against --set, so a mistyped or unsupported key (e.g. api_key vs apikey) was silently discarded instead of erroring. Any declared key not covered by --set still called input(), which raises EOFError with no stdin (e.g. under cron); that exception propagated up and was caught by main()'s broad except, returning 1 before save_config() ran and discarding any keys already collected in that call. Fix: - configure_module() now raises ValueError naming any --set key(s) that are not in the module's declared config_keys, instead of dropping them. - Add a --non-interactive flag. When set, configure_module() checks for missing required keys up front and raises ValueError naming them, instead of calling input() and risking EOFError. This gives README's 'Non-interactive configuration' a way to actually run under cron/unattended contexts. --- bin/cli.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/bin/cli.py b/bin/cli.py index 92267b1..1b6556c 100644 --- a/bin/cli.py +++ b/bin/cli.py @@ -673,7 +673,8 @@ def configure_module( modules: List[Dict[str, Any]], config_path: str, module_name: str, - set_values: Dict[str, str] + set_values: Dict[str, str], + non_interactive: bool = False ) -> int: module = next((m for m in modules if m.get("name") == module_name), None) if module is None: @@ -685,6 +686,21 @@ def configure_module( log(f"Module '{module_name}' does not declare configurable settings via introspection.") return 0 + unknown_keys = sorted(k for k in set_values if k not in config_keys) + if unknown_keys: + raise ValueError( + f"Unknown --set key(s) for module '{module_name}': {', '.join(unknown_keys)}. " + f"Valid keys: {', '.join(sorted(config_keys))}" + ) + + if non_interactive: + missing_keys = sorted(key for key in config_keys if key not in set_values) + if missing_keys: + raise ValueError( + f"--non-interactive requires --set for missing key(s) for module " + f"'{module_name}': {', '.join(missing_keys)}" + ) + updates: Dict[str, str] = {} for key in config_keys: if key in set_values: @@ -808,6 +824,12 @@ def main() -> int: action="append", help="With --configure-module, provide KEY=VALUE (can be repeated) to avoid prompts", ) + parser.add_argument( + "--non-interactive", + action="store_true", + help="With --configure-module, fail immediately (instead of prompting) if any " + "required key is missing from --set; suitable for cron/unattended use", + ) parser.add_argument( "--module", action="append", @@ -867,7 +889,10 @@ def main() -> int: if args.configure_module: try: set_values = parse_set_args(args.set) - return configure_module(modules, args.config_file, args.configure_module, set_values) + return configure_module( + modules, args.config_file, args.configure_module, set_values, + non_interactive=args.non_interactive + ) except Exception as e: print(f"[!] Unable to configure module: {e}", file=sys.stderr) return 1