From 8e24d5aaea1ca44bde08fe7f9b7a2541a4469fe6 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Fri, 27 Mar 2026 19:04:48 -0600 Subject: [PATCH] Fix tui_app_server resume-by-name lookup regression (#16050) Addresses #16049 `codex resume ` and `/resume ` could fail in the app-server TUI path because name lookup pre-filtered `thread/list` with the backend `search_term`, but saved thread names are hydrated after listing and are not part of that search index. Resolve names by scanning listed threads client-side instead, and add a regression test for saved sessions whose rollout title does not match the thread name. --- codex-rs/tui_app_server/src/lib.rs | 66 +++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 65c1fbb4e..a58dfc5fe 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -520,7 +520,10 @@ async fn lookup_session_target_by_name_with_app_server( source_kinds: Some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]), archived: Some(false), cwd: None, - search_term: Some(name.to_string()), + // Thread names are hydrated after `thread/list` resolves rollout metadata, so + // name-based resume must scan the filtered list client-side instead of relying on + // the backend search index. + search_term: None, }) .await?; if let Some(thread) = response @@ -1890,6 +1893,67 @@ mod tests { Ok(()) } + #[tokio::test] + async fn lookup_session_target_by_name_ignores_backend_search_term_mismatch() + -> color_eyre::Result<()> { + let temp_dir = TempDir::new()?; + let config = build_config(&temp_dir).await?; + let thread_id = ThreadId::new(); + let rollout_path = temp_dir + .path() + .join("sessions/2025/02/01") + .join(format!("rollout-2025-02-01T10-00-00-{thread_id}.jsonl")); + let rollout_dir = rollout_path.parent().expect("rollout parent"); + std::fs::create_dir_all(rollout_dir)?; + std::fs::write(&rollout_path, "")?; + + let state_runtime = codex_state::StateRuntime::init( + config.codex_home.clone(), + config.model_provider_id.clone(), + ) + .await + .map_err(std::io::Error::other)?; + state_runtime + .mark_backfill_complete(None) + .await + .map_err(std::io::Error::other)?; + + let session_cwd = temp_dir.path().join("project"); + std::fs::create_dir_all(&session_cwd)?; + let created_at = chrono::DateTime::parse_from_rfc3339("2025-02-01T10:00:00Z") + .expect("timestamp should parse") + .with_timezone(&chrono::Utc); + let mut builder = codex_state::ThreadMetadataBuilder::new( + thread_id, + rollout_path.clone(), + created_at, + SessionSource::Cli, + ); + builder.cwd = session_cwd; + let mut metadata = builder.build(config.model_provider_id.as_str()); + metadata.title = "Different rollout title".to_string(); + metadata.first_user_message = Some("preview text".to_string()); + state_runtime + .upsert_thread(&metadata) + .await + .map_err(std::io::Error::other)?; + + codex_core::append_thread_name(&config.codex_home, thread_id, "saved-session").await?; + + let mut app_server = + AppServerSession::new(codex_app_server_client::AppServerClient::InProcess( + start_test_embedded_app_server(config).await?, + )); + let target = + lookup_session_target_by_name_with_app_server(&mut app_server, "saved-session").await?; + let target = target.expect("name lookup should find the saved thread"); + assert_eq!(target.path, Some(rollout_path)); + assert_eq!(target.thread_id, thread_id); + + app_server.shutdown().await?; + Ok(()) + } + #[tokio::test] async fn embedded_app_server_start_failure_is_returned() -> color_eyre::Result<()> { let temp_dir = TempDir::new()?;