From 541c5bb95d2e8956cadbf4bd957a74f12940cfda Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 21 Apr 2026 18:04:04 +0900 Subject: [PATCH] feat: #139 actionable worker-state guidance in claw state error + help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously `claw state` errored with "no worker state file found ... — run a worker first" but there is no `claw worker` subcommand, so claws had no discoverable path from the error to a fix. Changes: - Rewrite the missing-state error to name the two concrete commands that produce .claw/worker-state.json: * `claw` (interactive REPL, writes state on first turn) * `claw prompt ` (one non-interactive turn) Also tell the user what to rerun: `claw state [--output-format json]`. - Expand the State --help topic with "Produces state", "Observes state", and "Exit codes" lines so the worker-state contract is discoverable before the user hits the error. - Add regression test state_error_surfaces_actionable_worker_commands_139 asserting the error contains `claw prompt`, REPL mention, and the rerun path, plus that the help topic documents the producer contract. Verified live: $ claw state error: no worker state file found at .claw/worker-state.json Hint: worker state is written by the interactive REPL or a non-interactive prompt. Run: claw # start the REPL (writes state on first turn) Or: claw prompt # run one non-interactive turn Then rerun: claw state [--output-format json] JSON mode preserves the full hint inside the error envelope so CI/claws can match on `claw prompt` without losing the canonical prefix. Full workspace test green except pre-existing resume_latest flake (unrelated). Closes ROADMAP #139. --- rust/crates/rusty-claude-cli/src/main.rs | 75 ++++++++++++++++++++---- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 178958e..d6fa4b8 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1690,14 +1690,21 @@ fn run_worker_state(output_format: CliOutputFormat) -> Result<(), Box + // Hint: worker state is written by the interactive REPL or a non-interactive prompt. + // Run: claw # start the REPL (writes state on first turn) + // Or: claw prompt # run one non-interactive turn + // Then rerun: claw state [--output-format json] return Err(format!( - "no worker state file found at {} — run a worker first", - state_path.display() + "no worker state file found at {path}\n Hint: worker state is written by the interactive REPL or a non-interactive prompt.\n Run: claw # start the REPL (writes state on first turn)\n Or: claw prompt # run one non-interactive turn\n Then rerun: claw state [--output-format json]", + path = state_path.display() ) .into()); } @@ -5400,11 +5407,13 @@ fn render_help_topic(topic: LocalHelpTopic) -> String { .to_string(), LocalHelpTopic::State => "State Usage claw state [--output-format ] - Purpose read the worker state file written by the interactive REPL + Purpose read .claw/worker-state.json written by the interactive REPL or a one-shot prompt Output worker id, model, permissions, session reference (text or json) Formats text (default), json - Prerequisite run `claw` interactively or `claw prompt ` to produce worker state first - Related ROADMAP #139 (worker-concept discoverability) · claw status" + Produces state `claw` (interactive REPL) or `claw prompt ` (one non-interactive turn) + Observes state `claw state` reads; clawhip/CI may poll this file without HTTP + Exit codes 0 if state file exists and parses; 1 with actionable hint otherwise + Related claw status · ROADMAP #139 (this worker-concept contract)" .to_string(), LocalHelpTopic::Export => "Export Usage claw export [--session ] [--output ] [--output-format ] @@ -9614,6 +9623,52 @@ mod tests { } } + #[test] + fn state_error_surfaces_actionable_worker_commands_139() { + // #139: the error for missing `.claw/worker-state.json` must name + // the concrete commands that produce worker state, otherwise claws + // have no discoverable path from the error to a fix. + let _guard = env_lock(); + let root = temp_dir(); + let cwd = root.join("project-with-no-state"); + std::fs::create_dir_all(&cwd).expect("project dir should exist"); + + let error = with_current_dir(&cwd, || { + super::run_worker_state(CliOutputFormat::Text).expect_err("missing state should error") + }); + let message = error.to_string(); + + // Keep the original locator so scripts grepping for it still work. + assert!( + message.contains("no worker state file found at"), + "error should keep the canonical prefix: {message}" + ); + // New actionable hints — this is what #139 is fixing. + assert!( + message.contains("claw prompt"), + "error should name `claw prompt ` as a producer: {message}" + ); + assert!( + message.contains("REPL"), + "error should mention the interactive REPL as a producer: {message}" + ); + assert!( + message.contains("claw state"), + "error should tell the user what to rerun once state exists: {message}" + ); + // And the State --help topic must document the worker relationship + // so claws can discover the contract without hitting the error first. + let state_help = render_help_topic(LocalHelpTopic::State); + assert!( + state_help.contains("Produces state"), + "state help must document how state is produced: {state_help}" + ); + assert!( + state_help.contains("claw prompt"), + "state help must name `claw prompt ` as a producer: {state_help}" + ); + } + #[test] fn parses_single_word_command_aliases_without_falling_back_to_prompt_mode() { let _guard = env_lock();