aaif-goose/goose · error · anyhow::Error
cannot determine the role of an empty conversation
Error message
cannot determine the role of an empty conversation
What it means
anyhow error from last_effective_role (crates/goose-agent/src/operation.rs) when the messages slice it inspects is empty. The function decides the next actor by looking at the last message's effective role; with zero messages there is no basis for a decision.
Source
Thrown at crates/goose-agent/src/operation.rs:41
.iter()
.rposition(|message| {
message.role == rmcp::model::Role::User
&& message.is_user_visible()
&& !message.is_tool_response()
})
.ok_or_else(|| anyhow!("state machine conversation has no kickoff message"))?;
Ok(&messages[start..])
}
pub fn trailing_error(conversation: &Conversation) -> Option<MessageErrorKind> {
conversation.last().and_then(Message::error_kind)
}
pub fn last_effective_role(messages: &[Message]) -> Result<EffectiveRole> {
messages
.last()
.map(effective_role)
.ok_or_else(|| anyhow!("cannot determine the role of an empty conversation"))
}
pub fn assistant_turn_count(messages: &[Message]) -> u32 {
let mut turns = 0;
let mut in_assistant_block = false;
for message in messages.iter().rev() {
if message.role == rmcp::model::Role::Assistant {
if !in_assistant_block {
turns += 1;
in_assistant_block = true;
}
} else {
in_assistant_block = false;
}
}
turns
}
View on GitHub (pinned to 3810898a74)
Solutions
- Guard the call site: skip or handle the empty case before computing the effective role
- Ensure the conversation has at least one message (kickoff user message) before entering logic that calls last_effective_role
- When trimming, never trim to zero — retain the kickoff message
- In tests, seed the slice with a message or assert the error deliberately
Example fix
// before
let role = last_effective_role(&messages)?;
// after
let role = match messages.last() {
Some(_) => last_effective_role(&messages)?,
None => EffectiveRole::User, // empty conversation: next speaker is the user side
}; Defensive patterns
Strategy: type-guard
Validate before calling
if messages.is_empty() { /* choose default role or skip */ } Type guard
fn non_empty_messages(messages: &[Message]) -> bool {
!messages.is_empty()
} Try / catch
let role = if messages.is_empty() {
EffectiveRole::User // empty conversation defaults to user's turn
} else {
last_effective_role(messages)?
}; Prevention
- Check emptiness before role-dependent logic
- Never trim a conversation to zero messages
- Seed new conversations with the kickoff message
When it happens
Trigger: Calling last_effective_role on an empty slice — typically messages_since_kickoff-style slices that came back empty (e.g. right after cleanup, or a session whose messages were all filtered out), or a freshly built Conversation with nothing pushed yet.
Common situations: First iteration of a machine loop before any message is appended; tests passing empty vecs; context-management trimming everything; deserialization producing an empty message list.
Related errors
- state machine conversation has no kickoff message
- state-machine session loaded without conversation
- No sessions found.
- Download is being cancelled; wait for it to finish before re
- Session has no conversation after history modification
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/f2af6349fec2869e.
Report an issue: GitHub.