Reduce stack pressure in session startup and config rebuilds (#25844)

## Why

`/clear` starts a fresh thread with `InitialHistory::Cleared`, which
re-enters the thread/session startup path. That path now builds large
async futures through `ThreadManagerState::spawn_thread_with_source`,
`Codex::spawn`, and `Session::new`. Separately, TUI config rebuilds for
cwd and permission-profile changes build a similarly heavy
`ConfigBuilder::build()` future inside the app task. In debug and Bazel
runs, those call chains can put enough state on the caller stack to
abort before startup or config refresh completes.

This change keeps the behavior the same while moving the heaviest future
frames off the caller stack.

## What changed

- Box `Codex::spawn(...)` in `codex-rs/core/src/thread_manager.rs`
before awaiting it from `spawn_thread_with_source`.
- Box `Session::new(...)` in `codex-rs/core/src/session/mod.rs` before
awaiting it from `Codex::spawn_internal`.
- Route `ConfigBuilder::build()` through a small `tokio::spawn` helper
in `codex-rs/tui/src/app/config_persistence.rs` so cwd and
permission-profile config rebuilds run on a runtime worker stack while
preserving error context.

## Verification

CI is running on the PR.

No new targeted tests were added. This is a mechanical stack-pressure
reduction that keeps the existing behavior and error propagation intact.
This commit is contained in:
jif-oai
2026-06-02 15:42:47 +02:00
committed by GitHub
Unverified
parent 33273e4258
commit 1dd731305a
3 changed files with 29 additions and 16 deletions
+2 -2
View File
@@ -625,7 +625,7 @@ impl Codex {
let session_source_clone = session_configuration.session_source.clone();
let (agent_status_tx, agent_status_rx) = watch::channel(AgentStatus::PendingInit);
let session = Session::new(
let session = Box::pin(Session::new(
session_configuration,
config.clone(),
installation_id,
@@ -647,7 +647,7 @@ impl Codex {
parent_rollout_thread_trace,
attestation_provider,
multi_agent_version,
)
))
.await
.map_err(|e| {
error!("Failed to create session: {e:#}");
+2 -2
View File
@@ -1308,7 +1308,7 @@ impl ThreadManagerState {
.await;
let CodexSpawnOk {
codex, thread_id, ..
} = Codex::spawn(CodexSpawnArgs {
} = Box::pin(Codex::spawn(CodexSpawnArgs {
config,
installation_id: self.installation_id.clone(),
auth_manager,
@@ -1336,7 +1336,7 @@ impl ThreadManagerState {
thread_store: Arc::clone(&self.thread_store),
attestation_provider: self.attestation_provider.clone(),
inherited_multi_agent_version: multi_agent_version,
})
}))
.await?;
let new_thread = self
.finalize_thread_spawn(codex, thread_id, tracked_session_source)
+25 -12
View File
@@ -14,19 +14,32 @@ pub(super) struct WindowsSetupPermissions {
pub(super) workspace_roots: Vec<AbsolutePathBuf>,
}
async fn build_config_on_runtime_worker(
builder: ConfigBuilder,
error_context: String,
) -> Result<Config> {
match tokio::spawn(async move { builder.build().await }).await {
Ok(build_result) => build_result.wrap_err(error_context),
Err(err) if err.is_panic() => std::panic::resume_unwind(err.into_panic()),
Err(err) => Err(err).wrap_err_with(|| format!("{error_context} task failed")),
}
}
impl App {
pub(super) async fn rebuild_config_for_cwd(&self, cwd: PathBuf) -> Result<Config> {
let mut overrides = self.harness_overrides.clone();
overrides.cwd = Some(cwd.clone());
let cwd_display = cwd.display().to_string();
ConfigBuilder::default()
let builder = ConfigBuilder::default()
.codex_home(self.config.codex_home.to_path_buf())
.cli_overrides(self.cli_kv_overrides.clone())
.harness_overrides(overrides)
.loader_overrides(self.loader_overrides.clone())
.build()
.await
.wrap_err_with(|| format!("Failed to rebuild config for cwd {cwd_display}"))
.loader_overrides(self.loader_overrides.clone());
build_config_on_runtime_worker(
builder,
format!("Failed to rebuild config for cwd {cwd_display}"),
)
.await
}
pub(super) async fn rebuild_config_for_permission_profile(
@@ -38,16 +51,16 @@ impl App {
overrides.sandbox_mode = None;
overrides.permission_profile = None;
overrides.default_permissions = Some(profile_id.to_string());
ConfigBuilder::default()
let builder = ConfigBuilder::default()
.codex_home(self.config.codex_home.to_path_buf())
.cli_overrides(self.cli_kv_overrides.clone())
.harness_overrides(overrides)
.loader_overrides(self.loader_overrides.clone())
.build()
.await
.wrap_err_with(|| {
format!("Failed to rebuild config for permission profile {profile_id}")
})
.loader_overrides(self.loader_overrides.clone());
build_config_on_runtime_worker(
builder,
format!("Failed to rebuild config for permission profile {profile_id}"),
)
.await
}
#[cfg(target_os = "windows")]