mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-13 17:36:44 +00:00
docs(roadmap): add #447 — JSON error envelopes go to stderr; stdout empty on error
Pinpoint: claw --no-such-flag --output-format json writes the JSON envelope to stderr (115 bytes) while stdout is 0 bytes. Same for missing_credentials, session_load_failed, invalid_model_syntax — all 4 error kinds tested put JSON on stderr. Breaks the standard 'output=$(cmd --output-format json)' pattern. Every major CLI (kubectl/gh/aws/jq/terraform -json) puts JSON on stdout regardless of success/failure. Sibling: deprecation warnings precede the JSON envelope on stderr, breaking 'tail -1 | jq' parsing.
This commit is contained in:
@@ -6416,3 +6416,6 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
|
||||
446. **Config is loaded 2-3 times per command invocation; each load re-emits identical deprecation warnings without deduplication — `status` triggers 3× `enabledPlugins` warning, `doctor`/`mcp` trigger 2× each, only `version` (config-free) emits 0** — dogfooded 2026-05-11 by Jobdori on `5a4cc506` in response to Clawhip pinpoint nudge at `1503388740595224717`. Reproduction: with a `~/.claw/settings.json` containing the deprecated `enabledPlugins` key, run each command from a fresh empty cwd and count `warning: ... is deprecated` lines on stderr — `claw status 2>&1 >/dev/null | grep -c deprecated` returns **3**, `claw doctor` returns **2**, `claw mcp` returns **2**, `claw version` returns **0**. Each duplicate is byte-identical (same file path, same line number, same field name). The pattern proves the config-load pipeline is invoked 2-3 times within a single command process; warnings are emitted at each load without checking a `warned_files: HashSet<PathBuf>` deduplication set. **Three sibling implications:** (a) **load-count varies by command** — status:3, doctor:2, mcp:2, version:0 — suggesting each command implements its own config-load call rather than going through a shared cached loader; (b) **noise pollution**: users running `claw status` once see the same 64-character warning 3 times in their terminal scrollback, making real warnings (other config errors, real deprecations) lost in the duplicate noise; (c) **performance signal**: 3× config load means 3× JSON parsing of `~/.claw/settings.json`, `~/.claw.json`, `$CLAW_CONFIG_HOME/settings.json`, and the project-local `.claw.json` / `.claw/settings.json` / `.claw/settings.local.json`. For a workspace with 5 config files, that's 15 redundant disk reads per status invocation. Earlier roadmap entries observed 3× (#424) and 4× (#425) warning counts at different HEADs; the count keeps fluctuating, suggesting the underlying issue is config-load fan-out that nobody has refactored. **Required fix shape:** (a) introduce a `ConfigLoader` cache scoped to the command-process lifetime: first load reads files and emits warnings; subsequent calls hit the cache and emit zero warnings; (b) move config validation/warnings to a single canonical entry point (`ConfigLoader::load_with_diagnostics()` returns `(RuntimeConfig, Vec<Warning>)` exactly once); (c) every command that needs config goes through the cached loader instead of re-reading from disk; (d) `doctor --output-format json` exposes `config_load_count:int` field so we can regression-test that loads are deduplicated; (e) regression test: any single command invocation emits each deprecation warning at most once. **Why this matters:** repeated identical warnings train users to ignore stderr noise. Real warnings (a new deprecation, a config error from a different file, an MCP server failure) get drowned out by 3-4 copies of the same notice. The 15-disk-read worst case is wasted I/O that adds startup latency. The fact that count fluctuates between HEADs (3 at `6c0c305a`, 4 at `d7dbe951`, back to 3 at `5a4cc506`) suggests dev velocity is moving config loads around without an architectural fix. Cross-references #424 (deprecation warning 3×), #425 (deprecation warning 4×), #421 (cwd canonicalization — possibly tied to per-load symlink resolution), #428 (default permission_mode loaded from same config files). Source: Jobdori live dogfood, `5a4cc506`, 2026-05-11.
|
||||
|
||||
|
||||
447. **All JSON error envelopes go to STDERR not STDOUT; stdout is empty (0 bytes) on every `--output-format json` failure — breaks the standard automation pattern `output=$(claw cmd --output-format json)` which captures nothing on error and forces ugly `2>&1` redirects to even see the JSON** — dogfooded 2026-05-11 by Jobdori on `5ab969e7` in response to Clawhip pinpoint nudge at `1503396289071808523`. Reproduction (stderr-vs-stdout discipline audit): `claw --no-such-flag --output-format json >stdout.txt 2>stderr.txt` → stdout = **0 bytes**, stderr = 115 bytes containing `{"error":"unknown option: --no-such-flag","hint":"Run \`claw --help\` for usage.","kind":"cli_parse","type":"error"}`. Same pattern across four error envelopes probed: (a) `cli_parse` → stdout 0 / stderr 115; (b) `missing_credentials` → stdout 0 / stderr 853 (includes deprecation warnings ahead of envelope); (c) `session_load_failed` → stdout 0 / stderr 322; (d) `invalid_model_syntax` → stdout 0 / stderr 199. Success paths route correctly: `claw status --output-format json` → stdout 1496 / stderr 0. **The asymmetry is wrong on two axes:** (a) **JSON-format outputs should always go to stdout regardless of success/failure**: every major CLI in this class (kubectl, gh, aws, jq, terraform `-json`, `npm --json`) emits JSON on stdout for both ok and error paths; consumers parse `stdout | jq .kind` and switch on the kind to detect errors. claw's split forces consumers to capture both streams or use `2>&1` which then includes deprecation prose alongside the JSON envelope and breaks parsing. (b) **Deprecation/info warnings leak into the JSON error envelope on stderr**: when stderr is the only path to get the JSON, the deprecation warning prefix (`warning: ... enabledPlugins ... is deprecated`) precedes the JSON, making `tail -1 stderr.txt | jq .` fragile. **Three sibling problems:** (i) **breaks the canonical Bash idiom** `if ! output=$(cmd --output-format json); then echo "$output" | jq .error; fi` — `$output` is empty on error so the `jq` call sees nothing. (ii) **forces N-line stderr parsing**: to get the JSON envelope from stderr, automation must read until EOF, then skip leading `warning:` lines, then parse only the last `{...}` JSON. This is a brittle heuristic that breaks if more warnings are added. (iii) **inconsistent with text mode**: text-mode error output ALSO goes to stderr (e.g., `claw --no-such-flag` → stderr `[error-kind: cli_parse]\nerror: ...`) — that's correct for text mode (stderr is the diagnostic channel). The bug is JSON mode inheriting the same routing. **Required fix shape:** (a) JSON error envelopes go to STDOUT when `--output-format json` is active; (b) keep text-mode error output on stderr (no change for text path); (c) deprecation/info warnings should ALSO go to stderr in JSON mode (they're diagnostic prose, not part of the JSON contract) — separate channels: JSON envelope on stdout, prose warnings on stderr; (d) add `--quiet` / `--no-warn` flag to fully suppress stderr warnings for clean automation; (e) regression test: every `--output-format json` failure path emits the JSON envelope on stdout, exit non-zero, no JSON ever on stderr. **Why this matters:** the entire point of `--output-format json` is enabling automation. Splitting JSON success vs error across stdout vs stderr defeats the purpose — automation must capture both, dedupe sources, and parse mixed streams. Cross-references #422 (exit-code parity across error envelopes), #424 (deprecation warnings noise), #428 (envelope vs prose tension), #446 (multi-load deprecation duplication). Source: Jobdori live dogfood, `5ab969e7`, 2026-05-11.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user