Compare commits

...

1 Commits

Author SHA1 Message Date
Guinness Chen
3a7314c5fe Make thread archive idempotent for archived threads 2026-04-23 16:47:52 -07:00
2 changed files with 84 additions and 1 deletions

View File

@@ -2945,7 +2945,7 @@ impl CodexMessageProcessor {
.thread_store
.read_thread(StoreReadThreadParams {
thread_id,
include_archived: false,
include_archived: true,
include_history: false,
})
.await

View File

@@ -165,6 +165,89 @@ async fn thread_archive_requires_materialized_rollout() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn thread_archive_is_idempotent_for_already_archived_thread() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri())?;
let thread_id = create_fake_rollout(
codex_home.path(),
"2025-01-01T00-00-00",
"2025-01-01T00:00:00Z",
"parent",
Some("mock_provider"),
/*git_info*/ None,
)?;
let rollout_path = find_thread_path_by_id_str(codex_home.path(), &thread_id)
.await?
.expect("active rollout path");
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let archive_id = mcp
.send_thread_archive_request(ThreadArchiveParams {
thread_id: thread_id.clone(),
})
.await?;
let archive_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(archive_id)),
)
.await??;
let _: ThreadArchiveResponse = to_response::<ThreadArchiveResponse>(archive_resp)?;
let archive_notification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("thread/archived"),
)
.await??;
let archived_notification: ThreadArchivedNotification = serde_json::from_value(
archive_notification
.params
.expect("thread/archived notification params"),
)?;
assert_eq!(archived_notification.thread_id, thread_id);
let archived_rollout_path = find_archived_thread_path_by_id_str(codex_home.path(), &thread_id)
.await?
.expect("archived rollout path");
assert_paths_match_on_disk(
&archived_rollout_path,
&codex_home
.path()
.join(ARCHIVED_SESSIONS_SUBDIR)
.join(rollout_path.file_name().expect("rollout file name")),
)?;
assert!(
find_thread_path_by_id_str(codex_home.path(), &thread_id)
.await?
.is_none(),
"thread should no longer have an active rollout"
);
let archive_again_id = mcp
.send_thread_archive_request(ThreadArchiveParams {
thread_id: thread_id.clone(),
})
.await?;
let archive_again_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(archive_again_id)),
)
.await??;
let _: ThreadArchiveResponse = to_response::<ThreadArchiveResponse>(archive_again_resp)?;
let archived_rollout_path_after =
find_archived_thread_path_by_id_str(codex_home.path(), &thread_id)
.await?
.expect("archived rollout path after second archive");
assert_paths_match_on_disk(&archived_rollout_path_after, &archived_rollout_path)?;
Ok(())
}
#[tokio::test]
async fn thread_archive_archives_spawned_descendants() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;