Route plugin command rendering through the same degraded config envelope used by status and MCP, falling back to empty runtime config when config loading fails so local plugin listing remains inspectable.
Constraint: Task 4 requires malformed MCP config consistency across status, doctor, mcp, and plugins surfaces.
Rejected: Hard-failing plugins on ConfigLoader errors | inconsistent with status/mcp degraded-mode contract and hides local plugin diagnostics.
Confidence: high
Scope-risk: narrow
Directive: Keep config_load_error/status fields aligned across local introspection commands when adding new config-dependent surfaces.
Tested: cargo test -p rusty-claude-cli malformed_mcp_config -- --nocapture; cargo test -p commands mcp_degrades_gracefully_on_malformed_mcp_config_144 -- --nocapture; cargo check -p rusty-claude-cli; cargo fmt --all -- --check; claw plugins --output-format json malformed-MCP smoke.
Not-tested: full workspace clippy remains blocked by pre-existing clippy warnings in runtime and rusty-claude-cli unrelated to this change.
Constraint: G007 worker integrations added plugin command surfaces but left the REPL handler referencing a pre-refactor variable.\nRejected: Revert the worker plugin-command surface | the parser/degraded-config changes are part of the G007 scope and only needed a narrow compile repair.\nConfidence: high\nScope-risk: narrow\nDirective: Keep plugin CLI and REPL command paths routed through plugins_command_payload_for so malformed config can degrade consistently.\nTested: cargo check --manifest-path rust/Cargo.toml -p runtime -p tools -p rusty-claude-cli -p commands -p plugins; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli parse_args_plugins -- --nocapture\nNot-tested: full G007 team suite pending worker completion\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>
Promote merge-ready green contracts from a level-only check to explicit provenance requirements for test commands, base freshness, recovery-attempt context, and known blocking flakes. This preserves simple level contracts while giving policy code a single satisfied-contract signal to require before merge decisions.\n\nConstraint: Task scope was limited to green_contract.rs, policy_engine.rs if needed, and narrow tests; stale_* and recovery_recipes.rs were not edited.\nRejected: Adding more boolean fields to GreenContract | clippy flagged the shape and a requirement list is more explicit.\nConfidence: high\nScope-risk: narrow\nDirective: Treat raw test level as insufficient for merge readiness unless green contract evidence is satisfied.\nTested: cargo check --manifest-path rust/Cargo.toml -p runtime; cargo test --manifest-path rust/Cargo.toml -p runtime; cargo clippy --manifest-path rust/Cargo.toml -p runtime -- -D warnings; focused green_contract, policy_engine, and integration tests.\nNot-tested: full workspace cargo test due pre-existing rusty-claude-cli session_lifecycle_prefers_running_process_over_idle_shell failure observed before this slice.
Add a runtime approval-token ledger so policy-blocked actions can require scoped owner grants, consume one-time tokens, reject replay, and retain delegation traceability.\n\nConstraint: Task 3 scope is the G004 approval-token chain for runtime event/report contract families.\nRejected: Extending the existing permission prompt path directly | the token contract can be tested independently without changing live tool authorization behavior.\nConfidence: high\nScope-risk: narrow\nDirective: Keep approval grants scoped to policy/action/repo/branch before wiring them into external execution paths.\nTested: cargo check --manifest-path rust/Cargo.toml --workspace; cargo test --manifest-path rust/crates/runtime/Cargo.toml; cargo test --manifest-path rust/crates/runtime/Cargo.toml approval_token -- --nocapture\nNot-tested: cargo clippy --manifest-path rust/crates/runtime/Cargo.toml --all-targets -- -D warnings is blocked by pre-existing warnings in compact.rs, file_ops.rs, policy_engine.rs, and sandbox.rs.
Constraint: G003 boot/session work adds a structured doctor boot-preflight check that must be visible in JSON output.
Rejected: reducing the doctor check count back to six | boot preflight is an explicit G003 acceptance surface.
Confidence: high
Scope-risk: narrow
Directive: Keep doctor/status JSON contract tests aligned with boot_preflight schema fields when extending preflight diagnostics.
Tested: git diff --check; cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo test --manifest-path rust/Cargo.toml -p runtime trusted_roots -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime startup -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime worker_boot -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools path_scope -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract -- --nocapture; cargo check --manifest-path rust/Cargo.toml --workspace
Not-tested: full cargo test --workspace remains deferred during active G003 team reconciliation.
Co-authored-by: OmX <omx@oh-my-codex.dev>
Canonicalize absolute shell path operands before comparing them with the workspace root so symlink-expanded reads cannot be downgraded under workspace-write enforcement. Also resolves local clippy findings in the touched tools crate so targeted linting can run cleanly.\n\nConstraint: Task 1 scope is workspace/path scope enforcement only; do not mutate .omx/ultragoal.\nRejected: Editing shared path-scope regression tests | worker-3 owns that test coverage and the current tests already prove the contract.\nConfidence: high\nScope-risk: narrow\nDirective: Keep shell/file permission classification canonical-path based before permitting workspace-write execution.\nTested: ../scripts/fmt.sh --check; cargo test -p tools --test path_scope_enforcement -- --nocapture; cargo test -p tools given_workspace_write_enforcer_when_bash -- --nocapture; cargo check -p tools; cargo clippy -p tools --all-targets --no-deps -- -D warnings\nNot-tested: Full workspace clippy still has known unrelated runtime crate warnings outside this task scope.
Constraint: G003 worker outputs added config and startup evidence fields that must compile under focused runtime validation before leader push.
Rejected: pushing auto-checkpoints without leader validation | integrated tests initially failed to compile due missing imports and stale StartupEvidenceBundle fixtures.
Confidence: high
Scope-risk: narrow
Directive: When extending StartupEvidenceBundle, update all in-crate fixtures in the same change.
Tested: git diff --check; cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo test --manifest-path rust/Cargo.toml -p runtime trusted_roots -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime startup -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime worker_boot -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools path_scope -- --nocapture; cargo check --manifest-path rust/Cargo.toml --workspace
Not-tested: full cargo test --workspace remains deferred during active G003 team work.
Co-authored-by: OmX <omx@oh-my-codex.dev>
Worker-3's path-scope regression showed outside read_file paths were blocked by the workspace wrapper after dispatch instead of by the permission enforcer. File, glob, and grep tools now classify path scope before dispatch and require danger-full-access for paths that resolve outside the current workspace.
Constraint: G002-alpha-security requires permission-mode event/status visibility for blocked file and shell paths
Rejected: relying only on runtime wrapper errors | it hides the active permission-mode denial contract from callers
Confidence: high
Scope-risk: narrow
Directive: keep path-sensitive tool permission classification aligned with workspace wrapper resolution
Tested: cargo test -p tools --test path_scope_enforcement --manifest-path rust/Cargo.toml --quiet; cargo test -p tools given_workspace_write_enforcer_when_bash --manifest-path rust/Cargo.toml --quiet; cargo check --manifest-path rust/Cargo.toml --workspace; cargo fmt --all --manifest-path rust/Cargo.toml -- --check
Not-tested: full workspace test suite after this small permission-classification follow-up
Co-authored-by: OmX <omx@oh-my-codex.dev>
Constraint: G002-alpha-security requires direct file-tool escapes to fail before reads while accepting the canonical runtime error text.
Rejected: weakening the test to accept successful reads | the verified behavior denies the escape and only the assertion vocabulary was stale.
Confidence: high
Scope-risk: narrow
Directive: Keep path-scope tests asserting denial semantics, not a single legacy wording.
Tested: cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo test --manifest-path rust/Cargo.toml -p tools path_scope -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools --test path_scope_enforcement -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime workspace_ -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract -- --nocapture; python3 -m pytest tests/test_security_scope.py -q; cargo check --manifest-path rust/Cargo.toml --workspace; git diff --check
Not-tested: full cargo test --workspace due known unrelated session_lifecycle_prefers_running_process_over_idle_shell failure.
Co-authored-by: OmX <omx@oh-my-codex.dev>
The G002 security gate caught that PowerShell path classification still treated Windows absolute paths as workspace-relative on POSIX, so workspace scope now rejects those tokens before permission downgrades.
Constraint: G002-alpha-security requires workspace/path scope across Windows path cases as well as direct paths, symlinks, globbing, shell expansion, and worktrees.
Rejected: Relying on PathBuf::is_absolute for Windows syntax on POSIX | it treats C:\ and UNC-like tokens as relative and weakens permission classification.
Confidence: high
Scope-risk: narrow
Directive: Keep bash and PowerShell path classifiers aligned whenever new shell syntax is admitted.
Tested: cargo test --manifest-path rust/Cargo.toml -p tools path_scope -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p tools --test path_scope_enforcement -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p runtime workspace_ -- --nocapture; python3 -m pytest tests/test_security_scope.py -q; cargo check --manifest-path rust/Cargo.toml --workspace.
Not-tested: Full cargo test --workspace still has existing unrelated rusty-claude-cli session lifecycle failure reported by workers.
Co-authored-by: OmX <omx@oh-my-codex.dev>
Cherry-pick from Yeachan-Heo's #2945 with manual conflict resolution:
- classify_skills_slash_command now catches -h/--help anywhere in args
- Restored pending_thinking parameter in push_output_block test calls
Co-authored-by: Yeachan-Heo <bellman@ultraworkers.dev>
Five interrelated fixes from parallel Hephaestus sessions:
1. fix(repl): display assistant text after spinner (#2981, #2982, #2937)
- Added final_assistant_text() call after run_turn spinner completes
- REPL now shows response text like run_prompt_json does
2. fix(compact): handle Thinking content blocks (#2985)
- Added ContentBlock::Thinking variant throughout compact summarizer
- Prevents panic when /compact encounters thinking blocks
3. fix(prompt): provider-aware model identity (#2822)
- New ModelFamilyIdentity enum (Claude vs Generic)
- Non-Anthropic models no longer say 'I am Claude'
- model_family_identity_for() detects provider and sets identity
4. fix(openai): preserve DeepSeek reasoning_content (#2821)
- Stream parser now captures reasoning_content from OpenAI-compat
- Emits ThinkingDelta/SignatureDelta events for reasoning models
- Thinking blocks included in conversation history for re-send
5. feat(runtime): Thinking block support across codebase
- AssistantEvent::Thinking variant in conversation.rs
- ContentBlock::Thinking in session serialization
- Thinking-aware compact summarization
- Tests for thinking block ordering and content
Closes#2981, #2982, #2937, #2985, #2822, #2821
* fix(mcp): exit 1 when JSON envelope contains ok:false
mcp info, mcp describe, and mcp list-filter all return
{"action":"error","ok":false,...} but previously exited 0,
requiring automation callers to inspect the envelope field.
After this fix: print_mcp detects ok:false in the rendered JSON
value and calls process::exit(1) after printing, so the exit code
reflects the semantic error in the envelope.
Unaffected: mcp list, mcp show, mcp help all have no ok field and
continue to exit 0 (they are not error paths).
Closes ROADMAP #68 (partial — agents bogus/mcp show nonexistent
found:false remain exit:0 as they use different envelope shapes).
* feat(scripts): add dogfood-build.sh — build from checkout and verify provenance
Builds claw from the current HEAD, then checks that the binary's
git_sha matches git rev-parse --short HEAD. Exits non-zero if the
binary is stale or provenance is opaque (git_sha: null).
Usage:
CLAW=$(bash scripts/dogfood-build.sh) # fail-fast if stale
$CLAW version --output-format json # provenance confirmed
Addresses ROADMAP #69: dogfooders using a stale installed binary
cannot attribute behavior to specific commits. This script makes
dogfood round zero unambiguous.
Also documents the safe workaround for contributors who have a
stale system-installed binary.
claw permissions list / claw permissions allow <tool> / claw permissions deny <tool>
all fell through to the prompt/LLM path because parse_subcommand had no
arm for "permissions". The single-word bare form was already intercepted
by bare_slash_command_guidance, but any form with rest.len() > 1 bypassed
the single-word guard and landed in the _other => CliAction::Prompt branch.
Fix: add a "permissions" arm in parse_subcommand that returns a structured
guidance Err so all multi-word forms get the same exit:1 + JSON error as
the bare single-word form, without any LLM call or session creation.
Verified: all invocation forms (bare, list, read-only, workspace-write,
allow/deny <tool>) exit 1 with kind:unknown guidance JSON. Zero sessions.
claw plugin list / claw marketplace / claw marketplace list all fell
through to the prompt/LLM path because parse_subcommand only matched
"plugins" (the primary name) while the canonical spec aliases
"plugin" and "marketplace" were unhandled.
This manifested as auth errors and session creation on direct
invocation — dogfood confirmed Gaebal's binary created one session
via plugin prompt fallback.
Fix: extend the plugins arm in parse_subcommand to also match
"plugin" | "marketplace" so all three forms route to the same
CliAction::Plugins without network calls or session creation.
Verified: all six forms (bare + list subcommand for each name) return
kind:plugin JSON, exit 0, and create zero sessions.
Closes ROADMAP #55 partial (plugins/marketplace bypass complete).
* fix: support /plugins slash command in resume mode
Move SlashCommand::Plugins out of the 'unsupported resumed slash
command' catch-all and add a handler arm in run_resume_command that
calls handle_plugins_slash_command for list/help actions.
Mutation actions (install/uninstall/enable/disable) are rejected with
a clear error since there is no runtime to reload in resume mode.
Add /plugins coverage to resumed_inventory_commands test in
output_format_contract.rs: kind, action, reload_runtime, target.
Before: claw --resume session.jsonl /plugins --output-format json
-> {error: 'unsupported resumed slash command', type: 'error'}, exit 1
After: claw --resume session.jsonl /plugins --output-format json
-> {kind: 'plugin', action: 'list', ...}, exit 0
* style: cargo fmt line wrap in run_resume_command plugins handler
* fix: block /plugins update in resume mode, fix comment
Address REQUEST_CHANGES from OMX review:
1. Add 'update' to the blocked mutation actions in resume mode
(previously only install/uninstall/enable/disable were blocked)
2. Fix comment: 'Only list is supported' instead of 'Only list/help'
since /plugins help doesn't actually parse as a valid action
* style: cargo fmt after conflict resolution
`claw config model --output-format json` and all other section subcommands
(`env`, `hooks`, `plugins`) returned identical output with no section field
— the section arg was parsed but discarded (_section parameter).
Fix: render_config_json now:
- Passes section through to handler
- Looks up the section value via runtime_config.get(), converting the
internal JsonValue to serde_json::Value via render()+parse
- Emits `section` (string) and `section_value` (JSON value or null)
in the response envelope
- Returns ok:false + error for unsupported section tokens
Test: config_section_json_emits_section_and_value asserts:
- No section field when no section arg
- section + section_value fields present for all known sections
- ok:false + error for unknown section
Pinpoint: ROADMAP #126
`claw skills show <name>`, `claw skills info <name>`, and
`claw skills list <filter>` were all falling through to
SkillSlashDispatch::Invoke, which spawned a real model session,
consumed tokens, and created session files.
Root cause: classify_skills_slash_command had no guards for
these discovery prefixes; every non-reserved arg became Invoke.
Fix:
- Add "show", "info" as Local-only bare tokens
- Add starts_with guards for "show ", "info ", "list " args
- handle_skills_slash_command: filter skill list by name/substring
for show/info/list-filter paths (no model call, no session)
- handle_skills_slash_command_json: same structured filtering
Test: skills_show_and_list_filter_do_not_invoke_model asserts
classify_skills_slash_command returns Local for all discovery
patterns and still returns Invoke for bare skill names.
Pinpoint: ROADMAP #502
resumed_status_command_emits_structured_json_when_requested was reading
the real ~/.claw/settings.json, causing loaded_config_files to be 1
instead of the expected 0 on machines with user config present.
Root cause: unlike other tests (e.g. resumed_config_command_loads_settings_files),
this test did not pass an isolated CLAW_CONFIG_HOME env var to run_claw,
so claw fell back to the real HOME and loaded the developer's settings file.
Fix: create a temp config-home dir and pass it as CLAW_CONFIG_HOME via
run_claw_with_env. This gives the assertion a clean 0-file baseline.
Unblocks PRs #2973, #2988, #2990 which all failed this same test on main.
Ref: ROADMAP #65
Resumed /agents --output-format json was returning a human-readable
text render wrapped in a JSON envelope field instead of the actual
structured agent list. The run_resume_command handler was calling
handle_agents_slash_command (text) for the json field instead of
handle_agents_slash_command_json.
Fix: use handle_agents_slash_command_json for the json outcome field,
matching the pattern already used by /skills and /plugins.
Test: extended resumed_inventory_commands_emit_structured_json_when_requested
to cover /agents, asserting kind=="agents", action=="list",
agents is an array, and count is a number (not a text render).
`claw version --output-format json` was missing build_date and
executable_path, making it impossible to identify which binary is
running or correlate it with a specific build/commit.
Fix: version_json_value() now includes:
- build_date: compile-time BUILD_DATE env (already in text output)
- executable_path: std::env::current_exe() at runtime
Test: version_emits_json_when_requested extended to assert both fields
are strings in the JSON envelope.
Pinpoint: ROADMAP #507
Add four assertions to inventory_commands_emit_structured_json_when_requested:
- kind == "plugin"
- action == "list"
- reload_runtime is boolean
- target is null when no plugin is targeted
Closes the only major --output-format json surface with zero contract
coverage. All other surfaces (agents, mcp, skills, status, sandbox,
doctor, help, version, acp, bootstrap-plan, system-prompt, init, diff,
config) already had test assertions.
Keep claw --help's resume-safe slash command summary aligned with the interactive command list by filtering STUB_COMMANDS and adding regression coverage.
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
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
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
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
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
## 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.
## 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.
## Problem
Two session error messages advertised `.claw/sessions/` as the managed-session
location, but the actual on-disk layout is `.claw/sessions/<workspace_fingerprint>/`
where the fingerprint is a 16-char FNV-1a hash of the CWD path.
Users see error messages like:
```
no managed sessions found in .claw/sessions/
```
But the real directory is:
```
.claw/sessions/8497f4bcf995fc19/
```
The error copy was a direct lie — it made workspace-fingerprint partitioning
invisible and left users confused about whether sessions were lost or just in
a different partition.
## Fix
Updated two error formatters to accept the resolved `sessions_root` path
and extract the actual workspace-fingerprint directory:
1. **format_missing_session_reference**: now shows the actual fingerprint dir
and explains that it's a workspace-specific partition
2. **format_no_managed_sessions**: now shows the actual fingerprint dir and
includes a note that sessions from other CWDs are intentionally invisible
Updated all three call sites to pass `&self.sessions_root` to the formatters.
## Examples
**Before:**
```
no managed sessions found in .claw/sessions/
```
**After:**
```
no managed sessions found in .claw/sessions/8497f4bcf995fc19/
Start `claw` to create a session, then rerun with `--resume latest`.
Note: claw partitions sessions per workspace fingerprint; sessions from other CWDs are invisible.
```
```
session not found: nonexistent-id
Hint: managed sessions live in .claw/sessions/8497f4bcf995fc19/ (workspace-specific partition).
Try `latest` for the most recent session or `/session list` in the REPL.
```
## Impact
- Users can now tell from the error message that they're looking in the right
directory (the one their current CWD maps to)
- The workspace-fingerprint partitioning stops being invisible
- Operators understand why sessions from adjacent CWDs don't appear
- Error copy matches the actual on-disk structure
## Tests
All 466 runtime tests pass. Verified on two real workspaces with actual
workspace-fingerprint directories.
Closes ROADMAP #80.
## Problem
When a user types `claw --model gpt-4` or `--model qwen-plus`, they get:
```
error: invalid model syntax: 'gpt-4'. Expected provider/model (e.g., anthropic/claude-opus-4-6) or known alias
```
USAGE.md documents that "The error message now includes a hint that names the detected env var" — but this hint does not actually exist. The user has to re-read USAGE.md or guess the correct prefix.
## Fix
Enhance `validate_model_syntax` to detect when a model name looks like it belongs to a different provider:
1. **OpenAI models** (starts with `gpt-` or `gpt_`):
```
Did you mean `openai/gpt-4`? (Requires OPENAI_API_KEY env var)
```
2. **Qwen/DashScope models** (starts with `qwen`):
```
Did you mean `qwen/qwen-plus`? (Requires DASHSCOPE_API_KEY env var)
```
3. **Grok/xAI models** (starts with `grok`):
```
Did you mean `xai/grok-3`? (Requires XAI_API_KEY env var)
```
Unrelated invalid models (e.g., `asdfgh`) do not get a spurious hint.
## Verification
- `claw --model gpt-4` → hints `openai/gpt-4` + `OPENAI_API_KEY`
- `claw --model qwen-plus` → hints `qwen/qwen-plus` + `DASHSCOPE_API_KEY`
- `claw --model grok-3` → hints `xai/grok-3` + `XAI_API_KEY`
- `claw --model asdfgh` → generic error (no hint)
## Tests
Added 3 new assertions in `parses_multiple_diagnostic_subcommands`:
- GPT model error hints openai/ prefix and OPENAI_API_KEY
- Qwen model error hints qwen/ prefix and DASHSCOPE_API_KEY
- Unrelated models don't get a spurious hint
All 177 rusty-claude-cli tests pass.
Closes ROADMAP #154.