fix: restore MCP startup progress messages in TUI (fixes #7827) (#7828)

## Problem

The introduction of `notify_sandbox_state_change()` in #7112 caused a
regression where the blocking call in `Session::new()` waits for all MCP
servers to fully initialize before returning. This prevents the TUI
event loop from starting, resulting in `McpStartupUpdateEvent` messages
being emitted but never consumed or displayed. As a result, the app
appears to hang during startup, and users do not see the expected
"Booting MCP server: {name}" status line.

Issue: [#7827](https://github.com/openai/codex/issues/7827)

## Solution
This change moves sandbox state notification into each MCP server's
background initialization task. The notification is sent immediately
after the server transitions to the Ready state. This approach:
- Avoids blocking `Session::new()`, allowing the TUI event loop to start
promptly.
- Ensures each MCP server receives its sandbox state before handling any
tool calls.
- Restores the display of "Booting MCP server" status lines during
startup.

## Key Changes
- Added `ManagedClient::notify_sandbox_state()` method.
- Passed sandbox_state to `McpConnectionManager::initialize()`.
- Sends sandbox state notification in the background task after the
server reaches Ready status.
- Removed blocking notify_sandbox_state_change() methods.
- Added a chatwidget snapshot test for the "Booting MCP server" status
line.

## Regression Details

Regression was bisected to #7112, which introduced the blocking
behavior.

---------

Co-authored-by: Michael Bolin <bolinfest@gmail.com>
Co-authored-by: Michael Bolin <mbolin@openai.com>
This commit is contained in:
Ivan Murashko
2025-12-12 22:07:03 +00:00
committed by GitHub
parent 54feceea46
commit c978b6e222
5 changed files with 109 additions and 31 deletions

View File

@@ -700,6 +700,14 @@ impl Session {
for event in events {
sess.send_event_raw(event).await;
}
// Construct sandbox_state before initialize() so it can be sent to each
// MCP server immediately after it becomes ready (avoiding blocking).
let sandbox_state = SandboxState {
sandbox_policy: session_configuration.sandbox_policy.clone(),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: session_configuration.cwd.clone(),
};
sess.services
.mcp_connection_manager
.write()
@@ -710,25 +718,10 @@ impl Session {
auth_statuses.clone(),
tx_event.clone(),
sess.services.mcp_startup_cancellation_token.clone(),
sandbox_state,
)
.await;
let sandbox_state = SandboxState {
sandbox_policy: session_configuration.sandbox_policy.clone(),
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: session_configuration.cwd.clone(),
};
if let Err(e) = sess
.services
.mcp_connection_manager
.read()
.await
.notify_sandbox_state_change(&sandbox_state)
.await
{
tracing::error!("Failed to notify sandbox state change: {e}");
}
// record_initial_history can emit events. We record only after the SessionConfiguredEvent is emitted.
sess.record_initial_history(initial_history).await;
@@ -840,14 +833,34 @@ impl Session {
sub_id: String,
updates: SessionSettingsUpdate,
) -> Arc<TurnContext> {
let session_configuration = {
let (session_configuration, sandbox_policy_changed) = {
let mut state = self.state.lock().await;
let session_configuration = state.session_configuration.clone().apply(&updates);
let sandbox_policy_changed =
state.session_configuration.sandbox_policy != session_configuration.sandbox_policy;
state.session_configuration = session_configuration.clone();
session_configuration
(session_configuration, sandbox_policy_changed)
};
let per_turn_config = Self::build_per_turn_config(&session_configuration);
if sandbox_policy_changed {
let sandbox_state = SandboxState {
sandbox_policy: per_turn_config.sandbox_policy.clone(),
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
sandbox_cwd: per_turn_config.cwd.clone(),
};
if let Err(e) = self
.services
.mcp_connection_manager
.read()
.await
.notify_sandbox_state_change(&sandbox_state)
.await
{
warn!("Failed to notify sandbox state change to MCP servers: {e:#}");
}
}
let model_family = self
.services
.models_manager