warpdotdev/warp · warning · anyhow::Error

Agent driver dropped while sending /exit

Error message

Agent driver dropped while sending /exit

What it means

Returned by the Claude Code harness exit() when ModelSpawner::spawn(...).await fails — the join/spawn channel dropped because the AgentDriver model it targets was released before the spawned update could run. It is an internal lifecycle race at shutdown, not a CLI failure: the /exit text never reached the terminal driver.

Source

Thrown at app/src/ai/agent_sdk/driver/harness/claude_code.rs:518

        setup_events
            .post_timeline_event(OzRunTimelineEvent::AgentStarted)
            .await;

        Ok(command_handle)
    }

    async fn exit(&self, foreground: &ModelSpawner<AgentDriver>) -> Result<()> {
        log::info!("Sending /exit to Claude Code CLI");
        let terminal_driver = self.terminal_driver.clone();
        foreground
            .spawn(move |_, ctx| {
                terminal_driver.update(ctx, |driver, ctx| {
                    driver.send_text_to_cli(CLAUDE_EXIT_COMMAND.to_string(), ctx);
                });
            })
            .await
            .map_err(|_| anyhow::anyhow!("Agent driver dropped while sending /exit"))
    }

    async fn handle_session_update(&self, _foreground: &ModelSpawner<AgentDriver>) -> Result<()> {
        self.handle_parent_bridge_session_update().await
    }

    async fn save_conversation(
        &self,
        save_point: SavePoint,
        foreground: &ModelSpawner<AgentDriver>,
    ) -> Result<()> {
        if matches!(save_point, SavePoint::Periodic)
            && !super::has_running_cli_agent(&self.terminal_driver, foreground).await
        {
            log::debug!("Will not save conversation, Claude Code not in progress");
            return Ok(());
        }

View on GitHub (pinned to e72fd7aacb)

Solutions

  1. Treat as non-fatal if the underlying session is already terminating — verify the claude CLI process is gone (ps aux | grep claude); kill it if it lingers.
  2. Ensure only one component drives exit/shutdown per session to avoid racing entity teardown.
  3. If processes leak regularly, capture logs around shutdown and report the lifecycle race against ModelSpawner/AgentDriver.

Example fix

// before
self.exit(&foreground).await?; // propagates 'dropped' error and fails shutdown

// after (tolerate dropped driver at exit)
if let Err(err) = self.exit(&foreground).await {
    log::warn!("failed to send /exit (driver likely gone): {err:#}");
}
Defensive patterns

Strategy: try-catch

Validate before calling

if foreground.is_dropped() { // or equivalent liveness check on the model handle
    log::info!("agent driver already gone; skipping /exit");
    return Ok(());
}

Try / catch

if let Err(err) = self.exit(&foreground).await {
    if err.to_string().contains("Agent driver dropped") {
        log::warn!("driver gone before /exit; assuming session terminated");
        return Ok(());
    }
    return Err(err);
}

Prevention

When it happens

Trigger: Invoking exit() on the claude_code harness (shutdown path) while the AgentDriver foreground model is being torn down concurrently — e.g. the session was killed/crashed first, or two shutdown paths race so the model handle is dropped before the spawned closure executes.

Common situations: Rapid double-exit (user quits while cleanup runs); agent process already dead so the framework drops the driver mid-exit; app shutdown tearing down entities while a harness exit future is still pending. Usually cosmetic: the CLI session is ending anyway.

Related errors


AI-assisted analysis of warpdotdev/warp@e72fd7aacb (2026-08-16). Data as JSON: /api/errors/a57ddba55d2a5f30. Report an issue: GitHub.