{"record":{"id":"3cdb78ec6b8a2696","repo":"aaif-goose/goose","slug":"state-machine-session-loaded-without-conversation","errorCode":null,"errorMessage":"state-machine session loaded without conversation","messagePattern":"state-machine session loaded without conversation","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/goose-agent/src/machine.rs","lineNumber":65,"sourceCode":"\npub struct StateMachine<'a, S, E = ConversationEffect> {\n    steps: Vec<Step<'a, S, E>>,\n    cancel: CancellationToken,\n}\n\nimpl<'a, S, E> StateMachine<'a, S, E>\nwhere\n    S: MachineSession,\n    E: MachineEffect + Send + 'static,\n{\n    pub fn new(steps: Vec<Step<'a, S, E>>, cancel: CancellationToken) -> Self {\n        Self { steps, cancel }\n    }\n\n    pub async fn step(&self, session: &S, emit: &Emitter) -> Result<Option<StepResult<E>>> {\n        let conversation = session\n            .conversation()\n            .ok_or_else(|| anyhow!(\"state-machine session loaded without conversation\"))?;\n\n        for step in &self.steps {\n            let name = step.operation().name();\n            let result = if self.cancel.is_cancelled() {\n                OperationResult::NotApplicable\n            } else {\n                let step_fut: OperationFuture<'_, Result<OperationResult<E>>> = match step {\n                    Step::Operation(operation) => operation.run(session, conversation, emit),\n                    Step::Inference(inference) => {\n                        let mut input = InferenceInput::default();\n                        for operation in self.steps.iter().map(|step| step.operation()) {\n                            input\n                                .tools\n                                .extend(operation.inference_tools(session).await?);\n                            input\n                                .prompt_parts\n                                .extend(operation.prompt_parts(session, conversation).await?);\n                            input","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-agent/src/machine.rs#L47-L83","documentation":"anyhow error returned by StateMachine::step (crates/goose-agent/src/machine.rs) when session.conversation() yields None. The MachineSession trait exposes the conversation as Option<&Conversation>; a loaded session that carries no conversation cannot be stepped, so the machine refuses before running any operations.","triggerScenarios":"Starting the state-machine agent path (GOOSE_STATE_MACHINE=1) against a session whose persisted data has no conversation section — an empty/newly created session record, a corrupted or truncated session file, or a session type that never stores conversations.","commonSituations":"Resuming a session file that was created but never received messages; hand-crafted or migrated session JSON missing the conversation key; tests constructing a bare MachineSession without attaching a Conversation.","solutions":["Start from a fresh session (drop --resume / the session id) so a conversation exists","Inspect the session file for a conversation field; if a migration dropped it, re-run migration or discard the session","If you implement MachineSession yourself, make conversation() return Some for any session you intend to step","Report/fix the loader if valid sessions are being persisted without conversation data"],"exampleFix":"// before\nimpl MachineSession for MySession {\n    fn conversation(&self) -> Option<&Conversation> { None }\n}\n\n// after\nimpl MachineSession for MySession {\n    fn conversation(&self) -> Option<&Conversation> { self.conv.as_ref() } // always Some for steppable sessions\n}","handlingStrategy":"type-guard","validationCode":"if let Some(conv) = session.conversation() { /* safe to step */ } else { start_new_session().await?; }","typeGuard":"fn has_conversation<S: MachineSession>(s: &S) -> bool {\n    s.conversation().is_some()\n}","tryCatchPattern":"match machine.step(&session, &emit).await {\n    Err(e) if e.to_string().contains(\"without conversation\") => {\n        // session record unusable: start fresh rather than retry\n        start_new_session().await?\n    }\n    other => other?,\n}","preventionTips":["Never persist a session record without a conversation","In tests, always attach a Conversation to fake sessions","Treat this error as data corruption: create a new session instead of retrying"],"tags":["rust","state-machine","session","goose-agent"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}