fix(#698): dedup config deprecation warnings per process; add tempfile dev-dep to runtime crate (fixes pre-existing test compile error)

This commit is contained in:
YeonGyu-Kim
2026-05-25 14:11:37 +09:00
parent c345ce6d02
commit b64df99134
3 changed files with 22 additions and 2 deletions

View File

@@ -7559,3 +7559,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
696. **`claw compact` (and likely other REPL-only commands) hangs indefinitely in non-interactive mode with no TTY — there is no timeout, no stdin-closed guard, and no `--output-format json` fast-exit path** — dogfooded 2026-05-25 on `bb2a9238`. Running `./rust/target/debug/claw compact --output-format json --help </dev/null` (or `compact` without `--help`) never returns: the process blocks waiting for interactive input that will never arrive. No timeout fires, no error is emitted on stdout, no `{"kind":"error",...}` JSON is produced. The caller must `kill` the process externally. This is a clawability gap: any orchestrator or claw that probes `compact` to discover its schema or trigger non-interactive compaction will hang until a watchdog kills it. **Required fix shape:** (a) detect stdin EOF / non-TTY at startup for REPL-only commands and emit `{"kind":"error","error_kind":"interactive_only","message":"claw compact requires an interactive terminal"}` with exit 1 instead of blocking; (b) honour `--output-format json` as an implicit non-interactive signal — if the flag is set and no TTY is present, exit immediately with a typed error; (c) add a global `--timeout <seconds>` flag or default 30 s watchdog for non-interactive invocations; (d) add regression coverage proving `claw compact </dev/null` exits non-zero within 1 s with a structured error. **Why this matters:** automation layers probe commands via `</dev/null` to discover schema; a hung process blocks the probe indefinitely and requires external timeout management, making compact undiscoverable and unscriptable. Source: Jobdori dogfood on `bb2a9238`, 2026-05-25.
697. **`claw plugins remove <name>` silently returns `status:"ok"` with exit 0 when the named plugin does not exist — no `not_found` error, no non-zero exit, no indication the operation was a no-op; sibling: `claw agents <unknown-subcommand>` returns `action:"help"` with exit 0 instead of a typed `unknown_subcommand` error** — dogfooded 2026-05-25 on `63a5a874`. Reproduction: `claw plugins remove nonexistent-plugin --output-format json </dev/null` returns `{"kind":"plugin","action":"remove","status":"ok","error":null,...}` and exits 0. No plugin named `nonexistent-plugin` exists. A caller cannot distinguish "plugin was successfully removed" from "plugin was never there" — both produce the same `status:"ok"` envelope. Sibling: `claw agents stop nonexistent-agent --output-format json </dev/null` returns `{"kind":"agents","action":"help","unexpected":"stop nonexistent-agent",...}` and exits 0 — unknown subcommand falls through to help output rather than a typed error with exit 1. **Required fix shape:** (a) `plugins remove` must check whether the named plugin exists before reporting success; emit `{"kind":"plugin","action":"remove","status":"error","error_kind":"plugin_not_found","plugin_name":"<name>"}` with exit 1 when the plugin is absent; (b) `agents <unknown>` must emit `{"kind":"agents","action":"error","error_kind":"unknown_subcommand","subcommand":"<token>","supported":["list","help"]}` with exit 1 instead of falling back to help output with exit 0; (c) add regression tests proving both paths exit 1 with typed error envelopes. **Why this matters:** idempotent-but-silent remove is fine for infrastructure tools with explicit idempotency contracts; claw has no such contract, and `status:"ok"` for a name-miss means automation cannot audit whether a remove actually ran vs was a no-op. Source: Jobdori dogfood on `63a5a874`, 2026-05-25.
698. **Config deprecation warnings emit once per `ConfigLoader::load()` call, so surfaces that call `load()` multiple times in a single invocation emit duplicate `warning:` lines to stderr — `claw plugins list` and `claw mcp list` each print the same deprecation warning twice** — dogfooded 2026-05-25 on `c345ce6d`. Reproduction: `echo '{"enabledPlugins": {}}' > ~/.claw/settings.json && claw plugins list 2>&1 | grep warning` prints the same `field "enabledPlugins" is deprecated. Use "plugins.enabled" instead` line twice. Root cause: `config.rs:304` emits `eprintln!("warning: {warning}")` for every warning in every `loader.load()` call; surfaces like `plugins_command_payload_for` and `render_mcp_report_json_for` each trigger an independent `loader.load()` (one for runtime config, one inside the command handler), multiplying the stderr output. `skills list` emits only one warning because its command path calls `load()` once; `plugins` and `mcp` emit two. **Required fix shape:** (a) track already-emitted warning strings in a process-lifetime `std::sync::OnceLock<Mutex<HashSet<String>>>` in `config.rs` and skip re-emitting duplicates within the same process run; or (b) collect all warnings at a single call site after all config loads are complete and emit once with dedup; or (c) change `load()` to return warnings alongside the result instead of eagerly printing them, letting call sites emit once. Option (a) is a minimal one-file fix. **Why this matters:** duplicate warnings make the CLI look buggy, cause CI log noise, and — when the deprecation warning fires on every invocation — are more likely to be `tail -f`'d away than acted on. A single clean warning per invocation is the standard. Source: Jobdori dogfood on `c345ce6d`, 2026-05-25.