Import only the continuation of backgrounded Claude sessions (#55365)

## Why

Backgrounding a Claude session creates a continuation that includes its history. Offering both sessions for import duplicates that history.

## What changed

Skip sessions containing a `continued-in` record with a nonblank `continuedInSessionId` during import detection, leaving the continuation available for import.

## Testing

Add detection and app-server regression tests covering continuation-only detection, importing the full history into one thread, and no remaining import candidates afterward.

GitOrigin-RevId: bf8383faadce984ae3c41a0b8e8b3072680fd4b2
This commit is contained in:
jgrodman-arch
2026-10-19 19:53:58 +00:00
committed by copyberry
parent 18d09e1eda
commit ee8b4ea181
3 changed files with 74 additions and 0 deletions
@@ -65,6 +65,7 @@ mod tests {
use super::*;
use crate::sessions::ledger::record_imported_session;
use codex_protocol::ThreadId;
use pretty_assertions::assert_eq;
use serde_json::Value as JsonValue;
use std::fs::FileTimes;
use std::fs::OpenOptions;
@@ -101,6 +102,45 @@ mod tests {
);
}
#[test]
fn detects_only_the_continuation_of_a_backgrounded_session() {
let root = TempDir::new().expect("tempdir");
let external_agent_home = root.path().join(".external");
let project_root = root.path().join("repo");
let original = record("user", "plan a vacation", &project_root);
write_session(
&external_agent_home,
&project_root,
"original.jsonl",
&[
original.clone(),
serde_json::json!({
"type": "continued-in",
"sessionId": "original",
"continuedInSessionId": "background",
}),
],
);
let continuation = write_session(
&external_agent_home,
&project_root,
"background.jsonl",
&[
original,
record("assistant", "Here is your itinerary", &project_root),
],
);
assert_eq!(
detect_recent_cla_sessions(&external_agent_home, root.path()).expect("detect"),
vec![ExternalAgentSessionMigration {
path: continuation,
cwd: project_root,
title: Some("plan a vacation".to_string()),
}]
);
}
#[test]
fn prefers_latest_custom_title_over_first_user_message() {
let root = TempDir::new().expect("tempdir");
@@ -40,6 +40,15 @@ pub fn summarize_session(path: &Path) -> io::Result<Option<SessionSummary>> {
let Ok(mut record) = serde_json::from_str::<JsonValue>(trimmed) else {
continue;
};
// Backgrounding replaces this session with a continuation that includes its history.
if record.get("type").and_then(JsonValue::as_str) == Some("continued-in")
&& record
.get("continuedInSessionId")
.and_then(JsonValue::as_str)
.is_some_and(|id| !id.trim().is_empty())
{
return Ok(None);
}
if cwd.is_none() {
cwd = record
.get("cwd")