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
2 changes: 2 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6428,3 +6428,5 @@ 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.

455. **`missing_credentials` JSON envelope serializes the provider-detection hint as a literal ` — hint: …` prose suffix appended to the `error` field, while the structured `hint` field always renders as `null` — automation that wants to read the adjacent-provider hint must regex the `error` prose string instead of reading `hint`** — dogfooded 2026-05-24 for the 07:00 Clawhip pinpoint nudge at message `1508001633416777778`, reproduced on local `./rust/target/debug/claw` `git_sha 003b739d` (origin/main `f8e1bb72`; the `MissingCredentials` Display impl in `rust/crates/api/src/error.rs:243-275` is unchanged in `63ce483c..f8e1bb72` which are docs-only ROADMAP additions). Repros in a fully clean isolated environment (`HOME=/tmp/iso5/home` with `{}` settings, fresh `/tmp/iso5/proj` git-init'd workspace, `stdin=/dev/null`, `ANTHROPIC_API_KEY` unset, `OPENAI_API_KEY` set): `claw -p 'hi' --output-format json` exits `1` with stderr JSON: `{"error":"missing Anthropic credentials; export ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY before calling the Anthropic API — hint: I see OPENAI_API_KEY is set — if you meant to use the OpenAI-compat provider, prefix your model name with `openai/` (e.g. `--model openai/gpt-4.1-mini`) so prefix routing selects the OpenAI-compatible provider, and set `OPENAI_BASE_URL` if you are pointing at OpenRouter/Ollama/a local server.","hint":null,"kind":"missing_credentials","type":"error"}`. Same shape with `XAI_API_KEY` set, suggesting an xAI model alias. With NO adjacent provider env set, `error` is short and `hint` is still `null` (consistent). The structured `hint` field is therefore **always `null`**, and the actual hint payload — which `ApiError::missing_credentials_with_hint` deliberately attaches — is only available via the `Display` impl's ` — hint: {hint}` suffix at `rust/crates/api/src/error.rs:269-272`, never serialized as a structured field. **Root cause (traced):** `MissingCredentials` carries `hint: Option<String>` and the `Display` impl writes ` — hint: {hint}` into the same string the JSON serializer uses as `error`, instead of routing the hint into a dedicated structured field. The existing JSON envelope already has a `hint` slot — but it is wired to a different (null) source, so the structured field never reflects the actual hint that the provider resolver computed. **Why this is distinct from existing items:** #322 (deprecation warnings on stderr breaking JSON parse), #340 (`/session help` JSON `type` vs `kind` vocabulary mismatch), #447 (all JSON error envelopes go to stderr not stdout), #450 (`prompt` missing_credentials JSON on stderr): those track envelope **transport** (stderr vs stdout) and **vocabulary** issues. This pinpoint is the **internal shape** of an existing envelope — even when transport and vocabulary are correct, the `hint` field is structurally dead because the hint is duplicated into the `error` prose. `ApiError`'s `missing_credentials_with_hint` constructor explicitly takes a hint argument, the unit tests at `error.rs:572-599` assert it ends with ` — hint: …`, and the JSON contract still emits `"hint":null`. **Why it matters:** the whole point of structured error envelopes is that automation can branch on `code`/`hint`/`kind` without scraping prose. Today, a claw that wants to detect "Anthropic missing but OpenAI key present → switch to `--model openai/...`" must regex the `error` string for ` — hint: I see OPENAI_API_KEY` instead of reading a typed `hint:{adjacent_provider:"openai", suggested_model_prefix:"openai/", suggested_base_url_env:"OPENAI_BASE_URL"}`. That regex is brittle: any future hint wording change silently breaks downstream automation. The prose hint also bloats the human-facing message; a structured field would let the human formatter and the machine consumer diverge cleanly. **Required fix shape:** (a) keep `Display` rendering for text mode (humans need the prose hint), but stop using `Display` as the JSON `error` field source — serialize a base `error` message (the canonical "missing X credentials" line without the appended hint prose) plus a structured `hint` object; (b) structure the hint as a typed enum/object such as `{kind:"adjacent_provider_detected", provider:"openai"|"xai"|"dashscope"|…, suggested_model_prefix, suggested_env_vars[], suggested_base_url_env}` so callers can branch on `hint.kind`; (c) keep `hint:null` only when there is genuinely no hint; (d) add regression coverage proving the JSON envelope has both a base `error` line and a non-null structured `hint` when `missing_credentials_with_hint` is constructed, and that the prose ` — hint: …` suffix does NOT appear inside the JSON `error` field; (e) add a parity test asserting that for every adjacent-provider hint path (OpenAI, xAI, DashScope, …) the structured `hint.kind` is set correctly. **Acceptance check (one-liner):** `claw -p 'hi' --output-format json 2>&1 | jq -e '.hint != null and (.error | test(" — hint: ") | not)'` should pass when an adjacent provider env var is set. Source: gaebal-gajae dogfood follow-up for the 2026-05-24 07:00 Clawhip pinpoint nudge at message `1508001633416777778`.
Loading