How Goose's Interactive Loop Handles Tool Execution Errors and Recovery

Goose captures tool execution failures at the dispatch layer and stream level, converting errors into ToolCallResult payloads or handling them via cancellation tokens, then returns control to the outer interactive loop to allow continued conversation without session termination.

The Goose AI agent framework from Block implements a resilient error-handling architecture in its interactive CLI mode that treats tool failures as recoverable message events rather than fatal crashes. When operating in interactive mode, the core session loop in crates/goose-cli/src/session/mod.rs receives a continuous stream of AgentEvent values from the language model provider, implementing distinct recovery paths for tool dispatch errors versus stream-level failures.

Error Capture Points in the Interactive Loop

The interactive session processes tool execution through two primary error capture mechanisms that ensure resilience against extension failures, network interruptions, or user cancellations.

Tool Dispatch Layer Error Handling

At the tool dispatch stage, errors are caught and transformed into structured response payloads within Agent::dispatch_tool_call (located in crates/goose/src/agents/agent.rs at lines 71–100).

The implementation wraps each tool invocation in a Result<ToolCallResult, ErrorData> type. When the ExtensionManager returns an error, the system:

  • Emits telemetry via posthog::emit_error when compiled with the telemetry feature flag
  • Downcasts the error into an ErrorData struct containing error codes and diagnostic messages
  • Returns the error as a ToolCallResult::from(Err(error_data)) variant

This approach treats failed tool calls as normal message content rather than exceptions, allowing the conversation flow to persist.

Stream-Level Failure Recovery

For catastrophic failures—such as network disconnections, authentication errors, or internal panics—the process_agent_response function (lines 1100–1110 in crates/goose-cli/src/session/mod.rs) handles the Some(Err(e)) arm of the async stream processing.

When the AgentEvent stream yields an error:

  1. handle_agent_error(&e, is_stream_json_mode) renders the failure to the terminal (or emits a JSON error event for programmatic consumers)
  2. The cancellation token triggers via cancel_token_clone.cancel() to abort ongoing async work
  3. The stream is explicitly dropped and the inner loop breaks
  4. Control returns to the outer interactive() loop (lines 12–34), preserving the session state

The Recovery Workflow and User Cancellation

Goose implements explicit recovery paths that distinguish between automatic error handling and user-initiated cancellations.

Graceful Degradation to Message Payloads

When a tool call fails during dispatch, the interactive loop receives the ToolCallResult containing ErrorData as a standard AgentEvent::Message payload. The session pushes this message onto self.messages and displays it as normal output (lines 1010–1025 in process_agent_response), enabling users to view error details and reformulate their requests without session interruption.

Handling User Cancellation and Permission Denial

For user-initiated cancellations via Permission::Cancel, the system injects a synthetic error response:

  • The agent receives a PermissionConfirmation with Permission::DenyOnce
  • A synthetic tool_response message is created with ErrorCode::INVALID_REQUEST and the message "Tool call cancelled by user"
  • The cancellation token triggers immediately to halt underlying extension processes
  • The stream drops and the inner loop breaks, returning to the outer interactive prompt

Telemetry and Observability Integration

When compiled with the telemetry feature, Goose automatically emits structured error events to PostHog via posthog::emit_error. This captures the tool name and error context at the dispatch layer, providing diagnostic visibility into extension failures without exposing sensitive data in the user interface.

Implementation Code Examples

The following patterns from crates/goose/src/agents/agent.rs demonstrate the error conversion logic:

// Dispatch a tool call with automatic error conversion
let result = self.extension_manager.dispatch_tool_call(&ctx, tool_call.clone(), token).await;
let tool_result = result.unwrap_or_else(|e| {
    #[cfg(feature = "telemetry")]
    crate::posthog::emit_error("tool_execution_failed", &format!("{}: {}", tool_call.name, e));
    
    let error_data = e.downcast::<ErrorData>()
        .unwrap_or_else(|e| ErrorData::new(ErrorCode::INTERNAL_ERROR, e.to_string(), None));
    
    ToolCallResult::from(Err(error_data))
});

The stream error handling in crates/goose-cli/src/session/mod.rs implements the recovery mechanism:

// Handling stream-level errors in the select! loop
Some(Err(e)) => {
    handle_agent_error(&e, is_stream_json_mode);  // User-facing error display
    cancel_token_clone.cancel();                // Abort ongoing operations
    drop(stream);                                 // Clean up the async stream
    break;                                        // Exit inner loop, preserve session
}

For user cancellations, the session injects structured error data:

// User cancellation injects synthetic error message
if permission == Permission::Cancel {
    self.agent.handle_confirmation(id.clone(), PermissionConfirmation {
        principal_type: PrincipalType::Tool,
        permission: Permission::DenyOnce,
    }).await;
    
    self.messages.push(Message::user().with_content(MessageContent::tool_response(
        id,
        Err(ErrorData { 
            code: ErrorCode::INVALID_REQUEST, 
            message: "Tool call cancelled by user".into(), 
            data: None 
        })
    )));
    
    cancel_token_clone.cancel();
    drop(stream);
    break;
}

Summary

  • Dual-layer error capture: Goose handles tool failures at both the dispatch layer (dispatch_tool_call) and the async stream level (process_agent_response) to ensure comprehensive error coverage.
  • Graceful degradation: Tool execution errors convert into standard ToolCallResult message payloads, allowing the conversation to continue without session termination.
  • Explicit cancellation: The cancellation token mechanism (cancel_token_clone.cancel()) immediately halts underlying async operations when errors occur or users abort requests.
  • Session continuity: After any error or cancellation, control returns to the outer interactive() loop in crates/goose-cli/src/session/mod.rs, preserving context and readying for the next user input.
  • Telemetry integration: Optional PostHog error emission provides observability into failure patterns while maintaining user privacy.

Frequently Asked Questions

What happens when a tool call fails during a Goose interactive session?

When a tool call fails, the error is caught in dispatch_tool_call within crates/goose/src/agents/agent.rs and converted into an ErrorData struct wrapped in a ToolCallResult. This result propagates as a normal message payload through the AgentEvent stream, allowing the interactive loop to display the error and continue the conversation rather than crashing the session.

How does Goose handle network or authentication errors in the event stream?

Stream-level failures—such as network disconnections or authentication errors—are caught in the Some(Err(e)) arm of the select! loop inside process_agent_response (lines 1100–1110). The system calls handle_agent_error to display the failure, triggers the cancellation token to abort ongoing work, drops the stream, and breaks the inner loop while preserving the outer interactive session.

Can users cancel an in-progress tool execution in Goose?

Yes. When a user selects Permission::Cancel, the session injects a synthetic tool_response message containing ErrorData with ErrorCode::INVALID_REQUEST and the message "Tool call cancelled by user". The system immediately triggers the cancellation token and drops the active stream, returning control to the input prompt without terminating the session.

Does Goose log tool execution errors for debugging purposes?

When compiled with the telemetry feature, Goose emits error events to PostHog via posthog::emit_error at the dispatch layer, capturing the tool name and error context. This occurs in dispatch_tool_call within crates/goose/src/agents/agent.rs, providing diagnostic visibility while treating the error as a recoverable event in the user interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →