Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6428,3 +6428,56 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)


450. **`prompt` emits `kind:"missing_credentials"` JSON on STDERR (not stdout), leaving stdout at 0 bytes — automation pattern `output=$(claw prompt hello --output-format json)` captures nothing on auth-absent failure; `doctor` correctly surfaces `auth.status:"warn"` with `api_key_present:false` but exposes no `prompt_ready:false` field that automation can check before invoking `prompt`** — dogfooded 2026-05-16 by Jobdori on `a35ee9a0` in response to Clawhip pinpoint nudge at `1505208225321062521`. Exact reproduction (isolated env, no creds, fresh git repo, HEAD `a35ee9a0`): `timeout 5 env -i HOME=$ISOLATED_HOME PATH=$PATH CLAW_CONFIG_HOME=$PROBE/.claw-cfg claw prompt hello --output-format json > stdout.txt 2> stderr.txt` → stdout = **0 bytes**, stderr = 195 bytes containing `{"error":"missing Anthropic credentials…","exit_code":1,"hint":null,"kind":"missing_credentials","type":"error"}`, exit code 1. Confirms Gaebal's `1505208553793781792` pinpoint that `prompt` timeout + zero bytes was the prior state — HEAD `a35ee9a0` now correctly exits 1 with `kind:"missing_credentials"` **but the envelope is still routed to stderr** (issue #447 class, same class as prior entries #422, #435). **Contrast with `doctor`:** `claw doctor --output-format json 2>/dev/null` succeeds to stdout with `checks[auth].status:"warn"`, `api_key_present:false`, `auth_token_present:false` — but the auth check has no `prompt_ready:false` field. Automation that gates on `doctor` before invoking `prompt` must re-derive readiness from `api_key_present && auth_token_present` — there is no single canonical boolean. **Three compound problems:** (a) **stdout-empty on `--output-format json` failure**: same class as #447; `prompt`'s error envelope goes to stderr, not stdout. The canonical automation idiom `if ! result=$(claw prompt "q" --output-format json); then echo "$result" | jq .kind; fi` sees `$result=""` on failure — the jq call gets nothing. All `--output-format json` error paths must route JSON to stdout per #447 contract; (b) **`doctor` missing `prompt_ready` field**: `doctor --output-format json` already knows auth is absent (`api_key_present:false`) but surfaces no derived `prompt_ready:bool` or `prompt_blocked_reason:string` field. Automation must infer readiness from `api_key_present || auth_token_present || legacy_*_present` — a 5-field OR across legacy fields that is fragile as auth mechanisms evolve. A single `prompt_ready:false` (with `prompt_blocked_reason:"auth_missing"`) inside the `auth` check would give downstream a stable contract; (c) **`claw prompt` with no auth does no preflight and fires straight at the API**: the preflight check that `doctor` runs (auth discovery) is not reused by `prompt` to emit a fast typed error before attempting the network call. Both Gaebal's pinpoint (prompt hanging silently on older HEAD) and the current behavior (prompt hitting auth gate after a brief API attempt) stem from the same root: prompt does not short-circuit at the point where `doctor` already knows auth is absent. If `doctor` can emit `kind:"doctor"` with `auth.status:"warn"` in ~20ms without a network call, `prompt` should emit `kind:"missing_credentials"` in the same window and output it to stdout. **Required fix shape:** (a) `prompt --output-format json` must write the `kind:"missing_credentials"` JSON envelope to **stdout**, not stderr — same fix as #447 for all error envelopes; (b) add `prompt_ready:bool` and `prompt_blocked_reason:string|null` to the `auth` check in `doctor --output-format json`; derive it as `api_key_present || auth_token_present || legacy_saved_oauth_present`; (c) `prompt` must run the credential preflight check (same codepath as doctor's auth check) before attempting any API call and emit `{"kind":"missing_credentials","prompt_blocked_reason":"auth_missing"}` on **stdout** with exit 1 if the check fails; (d) `--output-format json` stdout routing fix must cover: `prompt`, `session list` (cross-ref #449), `skills uninstall` (cross-ref #431), `resume` (cross-ref #435), `acp serve` (cross-ref #443) — the full `kind:"missing_credentials"` class; (e) regression test: `claw prompt hello --output-format json` with no creds writes JSON to stdout (0 bytes stderr), exits 1, `kind:"missing_credentials"`, in under 200ms (no network attempt). **Why this matters:** `prompt` is the primary consumer entry point. Auth-absent failure routing to stderr breaks every automation wrapper that captures `$(claw prompt ... --output-format json)`. The `doctor` preflight metadata gap means auth-readiness checks require parsing 5 legacy fields instead of reading one boolean. Cross-references #447 (all JSON error envelopes on stderr), #449 (session list hits auth gate), #431 (skills uninstall hits auth gate), #357 (auth gate on local ops cluster), #422 (exit-code parity). Source: Jobdori live dogfood, `a35ee9a0`, 2026-05-16.

464. **`--output-format` value parsing is strict-equal-only on `"text"`/`"json"` with five compounding gaps in `CliOutputFormat::parse` (`main.rs:600-611`): (1) **case-hostile** — `JSON`/`Json` rejected even though every UNIX enum-flag convention accepts both; (2) **whitespace-hostile** — `'json '` and `' json'` rejected, a known shell-interpolation footgun; (3) **no "Did you mean?" suggestion** for obvious near-matches like `JSON`/`json5`/`Json`; (4) **error format is always text-mode prose** — even when the operator is explicitly requesting JSON output (`--output-format=xml` → user clearly wanted machine-readable, gets text-prose error to stderr, chicken-and-egg bootstrap problem); (5) **classifier orphan** — the sentinel `"unsupported value for --output-format: ..."` is not registered in `classify_error_kind` so JSON-mode invocations would land with `kind: "unknown"` if it could ever reach that path. This is the second confirmed instance of the `#37×#77 classifier-orphan pattern` documented in #463 — features added without taxonomy registration silently degrade envelope quality** — dogfooded 2026-05-24 for the 14:30 Clawhip pinpoint nudge at message `1508114879985356992`, reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`). Live matrix from clean isolated env (`env -i HOME=/tmp/iso21/home PATH=/usr/bin:/bin TERM=dumb`):

```bash
$ for fmt in "xml" "JSON" "Json" "" "json5" "yaml" "json " " json" "JSON-LD"; do
printf -- '--- --output-format=%q ---\n' "$fmt"
claw --output-format "$fmt" status </dev/null 2>&1
echo "exit=$?"
done
```

Every one of those 9 inputs produces identical-shape text-mode stderr prose:

```
[error-kind: unknown]
error: unsupported value for --output-format: <VALUE> (expected text or json)

Run `claw --help` for usage.
```

Note `[error-kind: unknown]` literally leaks into the user-facing prose — a fragment of the classifier debug output, not a friendly hint. The same `[error-kind: unknown]` prefix shows up on `claw login` per #463 — confirming the orphan-sentinel pattern crosses surfaces.

**Per-input failure-mode breakdown:**

| Input | Why it's a real bug class |
|---|---|
| `JSON` | UNIX convention: enum flags are case-insensitive (`curl --request POST`/`post`; `git --color always`/`ALWAYS`; `cargo --color=always`/`Always`). Claw is in a tiny minority of case-hostile parsers. |
| `Json` | Same as above; mixed-case is common in TitleCase environments. |
| `'json '` / `' json'` | Standard shell-interpolation footgun: `OUTPUT_FORMAT=$(some-cmd)` may include trailing newline; `--output-format "$OUTPUT_FORMAT"` then injects whitespace. `.trim()` is one line of defense. |
| `''` | Empty value from unset env var: `--output-format "$UNSET_VAR"`. Specialized error ("empty value supplied for --output-format") would clarify. |
| `json5` / `yaml` / `xml` / `JSON-LD` | No "Did you mean: json?" suggestion. Slash commands have this; subcommand parser has this (now); enum-value parser does not. Asymmetric quality. |

**Root cause traced:** `rust/crates/rusty-claude-cli/src/main.rs:600-611`:

```rust
impl CliOutputFormat {
fn parse(value: &str) -> Result<Self, String> {
match value {
"text" => Ok(Self::Text),
"json" => Ok(Self::Json),
other => Err(format!(
"unsupported value for --output-format: {other} (expected text or json)"
)),
}
}
}
```

Five obvious enrichments missing: (a) `value.trim()` before match; (b) `value.to_lowercase()` before match; (c) levenshtein/prefix near-match suggestion; (d) specialized empty-value branch; (e) explicit `kind` discrimination so downstream classifier doesn't have to substring-match.

**The chicken-and-egg sub-bug (most severe):** A claw running `claw --output-format json status` to get structured output gets text-prose to stderr when the flag value is invalid. The very flag whose purpose is to request JSON cannot itself produce a JSON error envelope. The minimum fix is: if `--output-format` parsing fails AND any earlier argv contained `--output-format=json` OR `--output-format` with `json` as next arg (i.e., the operator's intent is clearly JSON), THEN emit the parse error as a JSON envelope on stderr instead of text prose. Bootstrap problem solved.

**Why distinct from existing items:** Surfaces a different facet of the broader **classifier-orphan / sentinel-not-typed pattern** documented in #463 (login/logout) and #455 (missing_credentials hint shape) and #449 (prompt JSON error routed to stderr). #463 covered the SAME root function (`classify_error_kind`) for a DIFFERENT sentinel ("has been removed"). This entry covers a DIFFERENT sentinel ("unsupported value for --output-format") with the SAME root, plus FOUR additional concerns specific to enum-value parsing (case, whitespace, near-match, empty). #109 covers config-loader warnings flattened to stderr prose (loader-side, not CLI-flag-side). #108 covers subcommand typos with no "Did you mean?" — analogous design pattern but different parser layer (subcommand name vs flag-value). **None** of these existing entries document the `CliOutputFormat::parse` strict-match-with-five-gaps surface. **Why this matters:** (1) **CI/automation reliability** — every CI step that constructs `--output-format $VAR` with VAR from another tool's output (Make, Justfile, shell completion, env file) is one trailing-newline or one casing mismatch away from text-prose-to-stderr instead of structured JSON. (2) **Bootstrap chicken-and-egg** is the worst flavor: the flag exists specifically to enable machine parsing, and the flag's own error path bypasses that machine parsing. (3) **Cross-surface asymmetry erodes trust** — slash commands suggest, subcommand parser now suggests (per #108 fix), but flag-enum parser doesn't. A claw learning the CLI grammar by trial-and-error gets contradictory teaching about the quality of suggestions. (4) **Same classifier-orphan pattern as #463** — `[error-kind: unknown]` text leakage proves the classifier-string roundtrip is happening even on simple flag-parse failures. The taxonomy gap from #77 is structurally chronic; per-instance fixes alone won't resolve it. (5) **Discovery method itself is a signal** — a 30-second hypothesis sweep covering 9 inputs caught 5 distinct bug classes. Strict-match parsers without trim+case+near-match+empty handling have a high latent-bug density per LoC. The classifier should ship as a typed result with structured envelope from day one. **Required fix shape:** (a) **`CliOutputFormat::parse` enrichments:** trim input; lowercase before match; explicit empty-value branch with specialized error; levenshtein-based "Did you mean?" suggestion (reuse the slash-command suggester from `commands::resolve_skill_invocation` or the subcommand suggester being added for #108). (b) **Bootstrap fix:** when `--output-format` parsing fails AND the operator's argv contains the literal token `json` adjacent to `--output-format`, emit the parse error as a JSON envelope to stderr so JSON-requesting consumers can decode it. (c) **Classifier registration:** add `"output_format_invalid"` to `classify_error_kind`, keyed on `"unsupported value for --output-format"`. (d) **Strip `[error-kind: ...]` debug prefix from text-mode user-facing output** — it's classifier internals leaking into the UX (also fixes #463's same complaint). (e) **Regression coverage in `output_format_contract.rs`:** parameterized test asserting 9 invalid inputs each produce the expected enriched behavior (case-normalized acceptance, trimmed acceptance, near-match suggestion, empty-value error, JSON-envelope bootstrap output when intent is JSON). **Acceptance check (one-liner):** `claw --output-format JSON status 2>&1 | jq -e '.kind == "output_format_invalid"'` should print `true` for the bootstrap-fix path; `claw --output-format JSON status; echo $?` should exit 0 after the case-normalization fix; `claw --output-format json5 status 2>&1 | grep -q "Did you mean: json"` should pass after the suggester fix. Source: gaebal-gajae dogfood for the 2026-05-24 14:30 Clawhip pinpoint nudge at message `1508114879985356992`. Pre-grep gate filtered 9 hypotheses → 2 fresh (W=`--output-format` value handling, Y=base URL validation); W selected because failures reproduce in credential-free path with multiple distinct sub-issues. Y deferred to a future tick because it requires actual HTTP-send path to surface failure, and the BASE_URL pinpoint is more about the `status`/`doctor` envelope failing to surface "configured to talk to malformed URL" warnings — different fix shape, different code path. Coordination note: Jobdori took #469 (`/compact` slash divergence) this tick and acknowledged F (CLAW_CONFIG_HOME validation, 5-mode silent failure surfaced in #463 tick) as "next confirmed but unfiled," so F is intentionally NOT covered here to avoid duplicate filing.
Loading