{"record":{"id":"58532a1ea647ed2d","repo":"aaif-goose/goose","slug":"no-user-message","errorCode":null,"errorMessage":"No user message","messagePattern":"No user message","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-cli/src/session/mod.rs","lineNumber":1440,"sourceCode":"\n    async fn process_agent_response(\n        &mut self,\n        interactive: bool,\n        cancel_token: CancellationToken,\n    ) -> Result<()> {\n        let is_json_mode = self.output_format == \"json\";\n        let is_stream_json_mode = self.output_format == \"stream-json\";\n\n        let session_config = SessionConfig {\n            id: self.session_id.clone(),\n            schedule_id: self.scheduled_job_id.clone(),\n            max_turns: self.max_turns,\n            retry_config: self.retry_config.clone(),\n        };\n        let user_message = self\n            .messages\n            .last()\n            .ok_or_else(|| anyhow::anyhow!(\"No user message\"))?;\n\n        let cancel_token_interrupt = cancel_token.clone();\n        let handle = tokio::spawn(async move {\n            if ctrl_c().await.is_ok() {\n                cancel_token_interrupt.cancel();\n            }\n        });\n        let _drop_handle = AbortOnDropHandle::new(handle);\n\n        let mut stream = self\n            .agent\n            .reply(\n                user_message.clone(),\n                session_config.clone(),\n                Some(cancel_token.clone()),\n            )\n            .await?;\n","sourceCodeStart":1422,"sourceCodeEnd":1458,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-cli/src/session/mod.rs#L1422-L1458","documentation":"reply() takes the last message in self.messages as the user turn to send to the provider. When the message list is empty there is no last element, so it fails with 'No user message' before any provider call is made.","triggerScenarios":"Headless or recipe paths (goose run, resumed sessions, scheduled jobs, custom harnesses) that call reply before any user message was pushed into the session.","commonSituations":"Recipes without an initial prompt; resumed sessions whose persisted history is empty; integrations constructing a CliSession and calling reply directly without adding a message.","solutions":["Push a user prompt into session.messages before invoking reply","For goose run, supply the prompt via flags/args so the session starts non-empty","If building on the API, assert !messages.is_empty() as a precondition"],"exampleFix":"// before\nlet reply = session.reply(&provider, cancel_token).await?;\n// after\nsession.add_message(Message::user().with_text(prompt.clone()));\nlet reply = session.reply(&provider, cancel_token).await?;","handlingStrategy":"validation","validationCode":"assert!(\n    !session.messages.is_empty(),\n    \"reply() requires at least one message; add the user prompt first\"\n);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always add the user message before calling reply","For headless runs, pass the prompt via flags or recipe so the session starts non-empty","Treat an empty message list as a caller bug, not a runtime condition"],"tags":["session","cli","internal-invariant","headless"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}