957 Commits

Author SHA1 Message Date
YeonGyu-Kim
af9723cf0a fix: #161 — wall-clock timeout for run_turn_loop; stalled turns now abort with stop_reason='timeout'
Previously, run_turn_loop was bounded only by max_turns (turn count). If
engine.submit_message stalled — slow provider, hung network, infinite
stream — the loop blocked indefinitely with no cancellation path. Claws
calling run_turn_loop in CI or orchestration had no reliable way to
enforce a deadline; the loop would hang until OS kill or human intervention.

Fix:
- Add timeout_seconds parameter to run_turn_loop (default None = legacy unbounded).
- When set, each submit_message call runs inside a ThreadPoolExecutor and is
  bounded by the remaining wall-clock budget (total across all turns, not per-turn).
- On timeout, synthesize a TurnResult with stop_reason='timeout' carrying the
  turn's prompt and routed matches so transcripts preserve orchestration context.
- Exhausted/negative budget short-circuits before calling submit_message.
- Legacy path (timeout_seconds=None) bypasses the executor entirely — zero
  overhead for callers that don't opt in.

CLI:
- Added --timeout-seconds flag to 'turn-loop' command.
- Exit code 2 when the loop terminated on timeout (vs 0 for completed),
  so shell scripts can distinguish 'done' from 'budget exhausted'.

Tests (tests/test_run_turn_loop_timeout.py, 6 tests):
- Legacy unbounded path unchanged (timeout_seconds=None never emits 'timeout')
- Hung submit_message aborted within budget (0.3s budget, 5s mock hang → exit <1.5s)
- Budget is cumulative across turns (0.6s budget, 0.4s per turn, not per-turn)
- timeout_seconds=0 short-circuits first turn without calling submit_message
- Negative timeout treated as exhausted (guard against caller bugs)
- Timeout TurnResult carries correct prompt, matches, UsageSummary shape

Full suite: 49/49 passing, zero regression.

Blocker: none. Closes ROADMAP #161.
2026-04-30 01:06:57 +09:00
YeonGyu-Kim
b88c899ceb feat(#160): wire claw list-sessions and delete-session CLI commands
Closes the last #160 gap: claws can now manage session lifecycle entirely
through the CLI without filesystem hacks.

New commands:
- claw list-sessions [--directory DIR] [--output-format text|json]
  Enumerates stored session IDs. JSON mode emits {sessions, count}.
  Missing/empty directories return empty list (exit 0), not an error.

- claw delete-session SESSION_ID [--directory DIR] [--output-format text|json]
  Idempotent: not-found is exit 0 with status='not_found' (no raise).
  Partial-failure: exit 1 with typed JSON error envelope:
    {session_id, deleted: false, error: {kind, message, retryable}}
  The 'session_delete_failed' kind is retryable=true so orchestrators
  know to retry vs escalate.

Public API surface extended in src/__init__.py:
- list_sessions, session_exists, delete_session
- SessionNotFoundError, SessionDeleteError

Tests added (tests/test_porting_workspace.py):
- test_list_sessions_cli_runs: text + json modes against tempdir
- test_delete_session_cli_idempotent: first call deleted=true,
  second call deleted=false (exit 0, status=not_found)
- test_delete_session_cli_partial_failure_exit_1: permission error
  surfaces as exit 1 + typed JSON error with retryable=true

All 43 tests pass. The session storage abstraction chapter is closed:
- storage layer decoupled from claw code (#160 initial impl)
- delete contract hardened + caller-audited (#160 hardening pass)
- CLI wired with idempotency preserved at exit-code boundary (this commit)
2026-04-30 01:06:57 +09:00
YeonGyu-Kim
e6ea4d248d fix(#160): harden delete_session contract — idempotency, race-safety, typed partial-failure
Addresses review feedback on initial #160 implementation:

1. delete_session() contract now explicit:
   - Idempotent: delete(x); delete(x) is safe, second call returns False
   - Race-safe: TOCTOU between exists()/unlink() eliminated via unlink-then-catch
   - Partial-failure typed: permission/IO errors wrapped in SessionDeleteError (OSError subclass)
     so callers can distinguish 'not found' (return False) from 'could not delete' (raise)

2. New SessionDeleteError class for partial-failure surfacing.
   Distinct from SessionNotFoundError (KeyError subclass for missing loads).

3. Caller audit confirmed: no code outside session_store globs .port_sessions
   or imports DEFAULT_SESSION_DIR. Storage layout is fully encapsulated.

4. Added tests/test_session_store.py — 18 tests covering:
   - list_sessions: empty/missing/sorted/non-json filter
   - session_exists: true/false/missing-dir
   - load_session: SessionNotFoundError typing (KeyError subclass, not FileNotFoundError)
   - delete_session idempotency: first/second/never-existed calls
   - delete_session partial-failure: SessionDeleteError wraps OSError
   - delete_session race-safety: concurrent deletion returns False, not raise
   - Full save->list->exists->load->delete roundtrip

All 18 tests pass. Merge-ready: contract documented, caller-audited, race-safe.
2026-04-30 01:06:57 +09:00
YeonGyu-Kim
0c600e76a7 fix: #160 — add list_sessions, session_exists, delete_session to session_store
- list_sessions(directory=None) -> list[str]: enumerate stored session IDs
- session_exists(session_id, directory=None) -> bool: check existence without FileNotFoundError
- delete_session(session_id, directory=None) -> bool: unlink a session file
- load_session now raises typed SessionNotFoundError (subclass of KeyError) instead of FileNotFoundError
- Claws can now manage session lifecycle without reaching past the module to glob filesystem

Closes ROADMAP #160. Acceptance: claw can call list_sessions(), session_exists(id), delete_session(id) without importing Path or knowing .port_sessions/<id>.json layout.
2026-04-30 01:06:57 +09:00
YeonGyu-Kim
424d5aff74 file: #161 — run_turn_loop has no wall-clock timeout, stalled turn blocks indefinitely 2026-04-30 01:06:57 +09:00
Bellman
f65b2b4f0e
Merge pull request #2861 from ultraworkers/docs/roadmap-341-tasks-json-dual-vocab
docs(roadmap): add #341 — tasks JSON error envelope uses dual vocabulary
2026-04-30 01:06:27 +09:00
Yeachan-Heo
f4b74e89dd Document why /tasks JSON errors need one stdout contract
Constraint: ROADMAP-only dogfood follow-up for 16:00 nudge on rebuilt claw git_sha 58569131
Rejected: code change in the command dispatcher | request was specifically to add one ROADMAP.md-only item
Confidence: high
Scope-risk: narrow
Directive: Keep /tasks distinct from #340; this is unsupported command stub JSON, not session help
Tested: git diff --check; scripts/fmt.sh --check
Not-tested: runtime behavior change, because this commit only documents the gap
2026-04-29 16:02:10 +00:00
Bellman
5856913104
Merge pull request #2859 from ultraworkers/docs/roadmap-340-session-help-json-stderr
docs(roadmap): add #340 — session help JSON error envelope goes to stderr
2026-04-30 00:54:42 +09:00
Yeachan-Heo
d45a0d2f5b Document stderr-only session help JSON contract gap
Capture the dogfood evidence as a roadmap item so the stdout JSON error-envelope contract can be fixed and regression-tested later.\n\nConstraint: User requested exactly one ROADMAP.md-only item #340 from current origin/main.\nConfidence: high\nScope-risk: narrow\nTested: git diff --check; scripts/fmt.sh --check\nNot-tested: Runtime behavior unchanged; documentation-only roadmap entry.
2026-04-29 15:31:59 +00:00
Bellman
dc47482e40
Merge pull request #2857 from ultraworkers/docs/roadmap-339-v2
docs(roadmap): add #339 — session delete not resume-safe, blocks GC automation
2026-04-30 00:26:29 +09:00
YeonGyu-Kim
9537c97231 docs(roadmap): add #339 — session delete not resume-safe, blocks GC automation 2026-04-30 00:18:28 +09:00
Bellman
f56a5afcf7
Merge pull request #2856 from ultraworkers/docs/roadmap-337-workspace-dirty-lifecycle-detail-restore
docs(roadmap): restore #337 workspace dirty lifecycle detail gap
2026-04-30 00:14:48 +09:00
Yeachan-Heo
3efaf551ed Restore roadmap GC lifecycle detail gap
Constraint: ROADMAP.md-only restore of lost #337 from PR #2852 / Jobdori dogfood evidence
Rejected: Renumbering adjacent items | preserving existing #338 and surrounding roadmap entries keeps history stable
Confidence: high
Scope-risk: narrow
Directive: Keep #337 before #338 and do not collapse the dirty-file detail requirement into the broader help/status backlog
Tested: git diff --check; scripts/fmt.sh --check
Not-tested: Product behavior changes; documentation-only change
2026-04-29 15:09:40 +00:00
Bellman
30c9b438ef
Merge pull request #2853 from ultraworkers/docs/roadmap-338-help-json-field-drift
docs(roadmap): add #338 for help JSON field drift
2026-04-30 00:06:24 +09:00
Yeachan-Heo
587bb18572 docs(roadmap): add #338 for help JSON field drift
Constraint: Respond to 14:30 dogfood nudge with one direct claw-code pinpoint.\nEvidence: rebuilt actual debug binary at git_sha 24ccb59b; compared top-level help --output-format json with resume-safe /help --output-format json.\nFinding: same help surface uses message in top-level JSON and text in slash/resume JSON.\nTested: cargo run --manifest-path rust/Cargo.toml --bin claw -- version --output-format json; ./rust/target/debug/claw help --output-format json; ./rust/target/debug/claw --resume latest /help --output-format json; git diff --check; scripts/fmt.sh --check.\nNot-tested: full Rust suite; roadmap-only documentation change.
2026-04-29 14:34:26 +00:00
Bellman
24ccb59bd2
Merge pull request #2851 from ultraworkers/docs/roadmap-329-slash-agents-json-opacity
docs(roadmap): add #329 for slash agents JSON opacity
2026-04-29 23:33:47 +09:00
Yeachan-Heo
0e8e75ef75 docs(roadmap): add #329 for slash agents JSON opacity
Constraint: Respond to dogfood nudge with exactly one concrete clawability pinpoint from direct claw-code use.\nEvidence: rebuilt actual debug binary at git_sha 0f7578c0; compared resume-safe /agents --output-format json with top-level claw agents --output-format json.\nFinding: slash /agents JSON only exposes kind,text while top-level agents JSON exposes structured agents[] inventory and provenance.\nTested: cargo run --manifest-path rust/Cargo.toml --bin claw -- version --output-format json; ./rust/target/debug/claw --resume latest /agents --output-format json; ./rust/target/debug/claw agents --output-format json; git diff --check; scripts/fmt.sh --check.\nNot-tested: full Rust suite; roadmap-only documentation change.
2026-04-29 14:01:36 +00:00
Bellman
0f7578c064
Merge pull request #2849 from ultraworkers/docs/roadmap-328-dogfood-pinpoint
Add ROADMAP #328 for native-agent source provenance
2026-04-29 22:35:51 +09:00
Yeachan-Heo
213d406cbf Record why native-agent provenance needs dogfood follow-up
Constraint: Scope requested ROADMAP.md only with exactly one new #328 pinpoint from direct claw dogfood.\nRejected: Implementing the agents-help fix now | user requested roadmap-only evidence item.\nConfidence: high\nScope-risk: narrow\nDirective: Keep agent help source roots derived from the same loader registry as agents list; do not hand-maintain a divergent root list.\nTested: cargo run --manifest-path rust/Cargo.toml --bin claw -- version --output-format json; ./rust/target/debug/claw version --output-format json; ./rust/target/debug/claw agents help --output-format json; ./rust/target/debug/claw agents --output-format json; git diff --check; scripts/fmt.sh --check\nNot-tested: Full Rust test suite; roadmap-only documentation change.
2026-04-29 13:33:23 +00:00
Bellman
ee85fed6ca
Merge pull request #2847 from ultraworkers/docs/roadmap-327-dogfood-pinpoint
Add ROADMAP #327 for MCP help source mismatch
2026-04-29 22:06:45 +09:00
Yeachan-Heo
3a34d83749 Record why MCP source help needs dogfood follow-up
Constraint: Scope limited to ROADMAP.md and one new pinpoint #327 from actual rebuilt claw dogfood.
Rejected: Code fix in this branch | user requested roadmap-only filing.
Confidence: high
Scope-risk: narrow
Directive: Keep mcp help source lists derived from actual config discovery, not hard-coded partial docs.
Tested: ./rust/target/debug/claw version --output-format json; ./rust/target/debug/claw mcp --help; ./rust/target/debug/claw mcp help --output-format json; temp .claw.json mcp list proof; git diff --check; scripts/fmt.sh --check
Not-tested: Full Rust test suite, documentation-only change.
2026-04-29 13:02:27 +00:00
Bellman
981aff7c8b
Merge pull request #2845 from ultraworkers/docs/roadmap-326-dogfood-pinpoint
docs(roadmap): add #326 pane inventory opacity pinpoint
2026-04-29 21:35:26 +09:00
Yeachan-Heo
c94940effa docs: add roadmap 326 pane inventory opacity 2026-04-29 12:33:36 +00:00
Bellman
b90875fa8e
Merge pull request #2843 from ultraworkers/docs/roadmap-325-help-json-schema
docs(roadmap): add #325 help json schema opacity pinpoint
2026-04-29 21:05:12 +09:00
Yeachan-Heo
2567cbcc78 Pin help JSON schema opacity for automation
Document the dogfood gap where help JSON stays parseable but hides command metadata inside a prose message, so future implementation can expose machine-readable command, slash-command, and resume-safety fields.\n\nConstraint: user requested ROADMAP.md-only pinpoint for issue #325 from origin/main d607ff36.\nRejected: implementing the schema now | requested fix shape is roadmap documentation only.\nConfidence: high\nScope-risk: narrow\nDirective: keep message for humans while adding schema/versioned structured help metadata when implementing.\nTested: git diff --check; scripts/fmt.sh --check\nNot-tested: runtime CLI behavior unchanged by docs-only change
2026-04-29 12:02:14 +00:00
Bellman
d607ff3674
Merge pull request #2840 from ultraworkers/docs/roadmap-324-stale-binary-provenance
docs(roadmap): add #324 stale binary provenance pinpoint
2026-04-29 20:34:27 +09:00
Yeachan-Heo
cdf6282965 Record why stale binary provenance needs a roadmap pin
Constraint: Documentation-only follow-up from current main e7074f47 after PR #2838; edit scope limited to ROADMAP.md.\nRejected: Implementing provenance detection now | user requested roadmap entry only.\nConfidence: high\nScope-risk: narrow\nDirective: Future implementation should compare embedded build git_sha/build date to workspace HEAD/dirty state without leaking secrets.\nTested: git diff --check; scripts/fmt.sh --check\nNot-tested: Runtime provenance behavior; this commit only records the roadmap requirement.
2026-04-29 11:31:19 +00:00
Bellman
e7074f47ee
Merge pull request #2838 from ultraworkers/docs/roadmap-322-323-clean
docs(roadmap): add #322 #323 — json stream corruption and session identity contradiction
2026-04-29 19:40:50 +09:00
YeonGyu-Kim
9468383b67 docs(roadmap): add #322 #323 — json stream corruption and session identity contradiction 2026-04-29 19:38:00 +09:00
Bellman
1da2781816
Merge pull request #2835 from ultraworkers/docs/roadmap-249-issue-github-oauth-opacity
docs(roadmap): add #249 issue GitHub OAuth opacity pinpoint
2026-04-29 19:31:50 +09:00
Yeachan-Heo
9037430d52 docs(roadmap): add #249 issue github oauth opacity pinpoint 2026-04-29 10:01:16 +00:00
Bellman
8e22f757d8
Merge pull request #2834 from ultraworkers/docs/roadmap-248-prompt-mode-silent-hang
docs(roadmap): add #248 prompt-mode silent-hang pinpoint
2026-04-29 18:31:48 +09:00
Yeachan-Heo
7676b376ae docs(roadmap): add #248 prompt-mode silent-hang pinpoint 2026-04-29 08:24:37 +00:00
Sigrid Jin (ง'̀-'́)ง oO
1011a83823
Merge pull request #2829 from ultraworkers/fix/issue-320-session-lifecycle-classification
Fix session lifecycle classification for idle tmux shells
2026-04-29 16:11:58 +09:00
Yeachan-Heo
1376d92064 Filter stub commands from resume-safe help
Keep claw --help's resume-safe slash command summary aligned with the interactive command list by filtering STUB_COMMANDS and adding regression coverage.
2026-04-29 03:31:34 +00:00
Yeachan-Heo
be53e04671 Classify saved sessions by live work rather than pane existence
Operator status previously treated any tmux pane in a workspace as equivalent to active work. The new classifier uses tmux pane command/path metadata as a soft signal, treats plain shells as idle, and adds dirty-worktree abandoned markers to status and session-list output for clawhip consumers.

Constraint: Keep issue #320 prototype minimal and additive without new dependencies

Rejected: Screen-scraping pane output | fragile and broader than needed for lifecycle classification

Confidence: high

Scope-risk: narrow

Tested: cargo test -p rusty-claude-cli

Tested: cargo check -p rusty-claude-cli

Not-tested: cargo clippy -p rusty-claude-cli --all-targets -- -D warnings is blocked by pre-existing commands crate clippy::unnecessary_wraps warnings
2026-04-28 13:12:37 +00:00
Yeachan-Heo
cb56dc12ab Document Rust formatting wrapper
Make scripts/fmt.sh robust to caller cwd and document it as the supported repo-root formatting entrypoint for the Rust workspace.
2026-04-28 09:38:46 +00:00
Yeachan-Heo
71686a20fc Resolve fmt wrapper path from its own directory
The formatting wrapper should remain safe when invoked through different current directories or shell contexts, so resolve the script directory before entering the Rust workspace and forwarding cargo fmt arguments.

Constraint: Wrapper must be runnable from repo root while forwarding flags like --check
Rejected: Leave relative dirname cd | less robust if invocation context changes
Confidence: high
Scope-risk: narrow
Tested: scripts/fmt.sh --check
Tested: git diff --check
2026-04-28 09:38:40 +00:00
Yeachan-Heo
07992b8a1b Make Rust formatting guidance runnable from repo root
The Rust crate layout expects formatting to run from the rust directory, so add a root-level wrapper that preserves the working command while forwarding user flags like --check. Documentation now points contributors at the wrapper instead of the misleading virtual-workspace manifest invocation.

Constraint: Root-level cargo fmt --manifest-path rust/Cargo.toml is misleading for this virtual workspace
Rejected: Document cd rust && cargo fmt directly | a root wrapper gives one stable repo-root command
Confidence: high
Scope-risk: narrow
Tested: scripts/fmt.sh --check
Tested: git diff --check
2026-04-28 09:38:08 +00:00
Yeachan-Heo
74ea754d29 Restore Rust formatting compliance
Run rustfmt from the Rust workspace so CI format checks pass without changing behavior.

Constraint: Scope is formatting-only across tracked Rust files

Confidence: high

Scope-risk: narrow

Tested: cd rust && cargo fmt --check

Tested: git diff --check
2026-04-28 09:19:16 +00:00
Yeachan-Heo
77afde768c Clarify allowed tool status handling
Reject empty --allowedTools inputs instead of treating them as an empty restriction, and surface status JSON metadata that distinguishes default unrestricted tools from flag-provided allow lists.

Confidence: high
Scope-risk: narrow
Tested: cargo test -p rusty-claude-cli rejects_empty_allowed_tools_flag -- --nocapture
Tested: cargo test -p tools allowed_tools_rejects_empty_token_lists -- --nocapture
Tested: cargo check -p rusty-claude-cli -p tools
Tested: cargo test -p rusty-claude-cli -p tools
Not-tested: full workspace cargo fmt --check is blocked by pre-existing unrelated formatting drift
2026-04-28 05:44:14 +00:00
Yeachan-Heo
6db68a2baa Expose tool permission gates as structured worker blockers
Worker boot could previously stall on an interactive MCP/tool permission prompt while readiness and startup-timeout surfaces only had generic idle/no-evidence shapes. This adds a first-class blocked lifecycle state, structured event payload, startup evidence fields, and regression coverage so callers can report the exact server/tool gate instead of pane-scraping.

Constraint: ROADMAP #200 requires tool/server identity, prompt age, and session-only versus always-allow capability in status/evidence surfaces
Rejected: Treat MCP/tool prompts as trust gates | conflates distinct prompts and loses tool identity
Rejected: Leave allow-scope as pane text only | clawhip still could not classify the blocker without scraping
Confidence: high
Scope-risk: moderate
Directive: Keep tool_permission_required distinct from trust_required; downstream claws rely on server/tool payload plus allow-scope metadata
Tested: cargo test -p runtime tool_permission
Tested: cargo fmt -p runtime -- --check && cargo clippy -p runtime --all-targets -- -D warnings && cargo test -p runtime
Tested: cargo test --workspace
Not-tested: live interactive MCP permission prompt in tmux
2026-04-27 09:28:09 +00:00
Yeachan-Heo
5b910356a2 Preserve trust boundaries during pulled follow-up
The pull brought the branch current with origin/main while replaying local follow-up work. Conflict resolution kept the roadmap/progress additions and integrated the runtime event/trust changes with upstream's newer surfaces.

The trust allowlist now treats worktree_pattern as an additional required predicate, including the missing-worktree case, so auto-trust cannot fall back to cwd-only matching when a worktree constraint was declared. The runtime formatting cleanup keeps clippy/fmt green after the merge.

Constraint: Local branch was 109 commits behind origin/main with dirty tracked follow-up work.

Rejected: Drop the autostash after conflict resolution | keeping it preserves a reversible safety backup for unrelated recovery.

Confidence: high

Scope-risk: moderate

Directive: Do not relax worktree_pattern matching without preserving the missing-worktree regression.

Tested: git diff --cached --check; cargo fmt -p runtime -- --check; cargo clippy -p runtime --all-targets -- -D warnings; cargo test -p runtime; cargo test --workspace; architect verification approved

Not-tested: Live tmux/worker auto-trust behavior outside unit/integration tests
2026-04-27 09:05:50 +00:00
YeonGyu-Kim
a389f8dff1 file: #160 — session_store missing list_sessions, delete_session, session_exists — claw cannot enumerate or clean up sessions without filesystem hacks 2026-04-22 08:47:52 +09:00
YeonGyu-Kim
7a014170ba file: #159 — run_turn_loop hardcodes empty denied_tools, permission denials absent from multi-turn sessions 2026-04-22 06:48:03 +09:00
YeonGyu-Kim
986f8e89fd file: #158 — compact_messages_if_needed drops turns silently, no structured compaction event 2026-04-22 06:37:54 +09:00
YeonGyu-Kim
ef1cfa1777 file: #157 — structured remediation registry for error hints (Phase 3 of #77)
## Gap

#77 Phase 1 added machine-readable error kind discriminants and #156 extended
them to text-mode output. However, the hint field is still prose derived from
splitting existing error text — not a stable registry-backed remediation
contract.

Downstream claws inspecting the hint field still need to parse human wording
to decide whether to retry, escalate, or terminate.

## Fix Shape

1. Remediation registry: remediation_for(kind, operation) -> Remediation struct
   with action (retry/escalate/terminate/configure), target, and stable message
2. Stable hint outputs per error class (no more prose splitting)
3. Golden fixture tests replacing split_error_hint() string hacks

## Source

gaebal-gajae dogfood sweep 2026-04-22 05:30 KST
2026-04-22 05:31:00 +09:00
YeonGyu-Kim
f1e4ad7574 feat: #156 — error classification for text-mode output (Phase 2 of #77)
## Problem

#77 Phase 1 added machine-readable error `kind` discriminants to JSON error
payloads. Text-mode (stderr) errors still emit prose-only output with no
structured classification.

Observability tools (log aggregators, CI error parsers) parsing stderr can't
distinguish error classes without regex-scraping the prose.

## Fix

Added `[error-kind: <class>]` prefix line to all text-mode error output.
The prefix appears before the error prose, making it immediately parseable by
line-based log tools without any substring matching.

**Examples:**

## Impact

- Stderr observers (log aggregators, CI systems) can now parse error class
  from the first line without regex or substring scraping
- Same classifier function used for JSON (#77 P1) and text modes
- Text-mode output remains human-readable (error prose unchanged)
- Prefix format follows syslog/structured-logging conventions

## Tests

All 179 rusty-claude-cli tests pass. Verified on 3 different error classes.

Closes ROADMAP #156.
2026-04-22 00:21:32 +09:00
YeonGyu-Kim
14c5ef1808 file: #156 — error classification for text-mode output (Phase 2 of #77)
ROADMAP entry for natural Phase 2 follow-up to #77 Phase 1 (JSON error kind
classification). Text-mode errors currently prose-only with no structured
class; observability tools parsing stderr need the kind token.

Two implementation options:
- Prefix line before error prose: [error-kind: missing_credentials]
- Suffix comment: # error_class=missing_credentials

Scope: ~20 lines. Non-breaking (adds classification, doesn't change error text).

Source: Cycle 11 dogfood probe at 23:18 KST — product surface clean after
today's batch, identified natural next step for error-classification symmetry.
2026-04-21 23:19:58 +09:00
YeonGyu-Kim
9362900b1b feat: #77 Phase 1 — machine-readable error classification in JSON error payloads
## Problem

All JSON error payloads had the same three-field envelope:
```json
{"type": "error", "error": "<prose with hint baked in>"}
```

Five distinct error classes were indistinguishable at the schema level:
- missing_credentials (no API key)
- missing_worker_state (no state file)
- session_not_found / session_load_failed
- cli_parse (unrecognized args)
- invalid_model_syntax

Downstream claws had to regex-scrape the prose to route failures.

## Fix

1. **Added `classify_error_kind()`** — prefix/keyword classifier that returns a
   snake_case discriminant token for 12 known error classes:
   `missing_credentials`, `missing_manifests`, `missing_worker_state`,
   `session_not_found`, `session_load_failed`, `no_managed_sessions`,
   `cli_parse`, `invalid_model_syntax`, `unsupported_command`,
   `unsupported_resumed_command`, `confirmation_required`, `api_http_error`,
   plus `unknown` fallback.

2. **Added `split_error_hint()`** — splits multi-line error messages into
   (short_reason, optional_hint) so the runbook prose stops being stuffed
   into the `error` field.

3. **Extended JSON envelope** at 4 emit sites:
   - Main error sink (line ~213)
   - Session load failure in resume_session
   - Stub command (unsupported_command)
   - Unknown resumed command (unsupported_resumed_command)

## New JSON shape

```json
{
  "type": "error",
  "error": "short reason (first line)",
  "kind": "missing_credentials",
  "hint": "Hint: export ANTHROPIC_API_KEY..."
}
```

`kind` is always present. `hint` is null when no runbook follows.
`error` now carries only the short reason, not the full multi-line prose.

## Tests

Added 2 new regression tests:
- `classify_error_kind_returns_correct_discriminants` — all 9 known classes + fallback
- `split_error_hint_separates_reason_from_runbook` — with and without hints

All 179 rusty-claude-cli tests pass. Full workspace green.

Closes ROADMAP #77 Phase 1.
2026-04-21 22:38:13 +09:00