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();