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

> Goose's interactive loop expertly handles tool execution errors converting failures to ToolCallResult payloads or cancellation tokens. Recover gracefully and continue conversations without session termination.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: internals
- Published: 2026-04-05

---

**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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/crates/goose/src/agents/agent.rs) demonstrate the error conversion logic:

```rust
// 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`](https://github.com/block/goose/blob/main/crates/goose-cli/src/session/mod.rs) implements the recovery mechanism:

```rust
// 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:

```rust
// 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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/crates/goose/src/agents/agent.rs), providing diagnostic visibility while treating the error as a recoverable event in the user interface.