mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Persist session IDs across thread resume (#29327)
## Summary
A cold-resumed subagent kept its durable thread ID but could receive a
new session ID, splitting one agent tree across multiple sessions after
a restart.
Persist the root session ID in every rollout `SessionMeta`, carry it
through thread creation, and restore it before initializing the resumed
`Session` and `AgentControl`.
## Behavior
For a nested agent tree:
```text
root session R
parent thread P
child thread C
```
The child rollout stores:
```text
session_id: R
parent_thread_id: P
id: C
```
After a cold resume, the child still belongs to root session `R` while
its immediate parent remains `P`. The integration coverage uses distinct
values for all three IDs so it catches restoring the session from
`parent_thread_id`.
## Legacy rollouts
Previous rollouts have `id` but no `session_id`. `SessionMetaLine`
deserialization treats a missing `session_id` as `id`, keeping those
files readable, listable, and resumable. When a legacy subagent is
resumed through its root, that synthesized child ID no longer overrides
the inherited root-scoped `AgentControl`. New rollouts always persist
the explicit root session ID.
This commit is contained in:
@@ -57,7 +57,9 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use serde::de::Error as _;
|
||||
use serde_json::Value;
|
||||
use serde_with::serde_as;
|
||||
use strum_macros::Display;
|
||||
@@ -2914,6 +2916,7 @@ pub enum MultiAgentVersion {
|
||||
/// and should be used when there is no config override.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
|
||||
pub struct SessionMeta {
|
||||
pub session_id: SessionId,
|
||||
pub id: ThreadId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub forked_from_id: Option<ThreadId>,
|
||||
@@ -2956,8 +2959,10 @@ pub struct SessionMeta {
|
||||
|
||||
impl Default for SessionMeta {
|
||||
fn default() -> Self {
|
||||
let id = ThreadId::default();
|
||||
SessionMeta {
|
||||
id: ThreadId::default(),
|
||||
session_id: id.into(),
|
||||
id,
|
||||
forked_from_id: None,
|
||||
parent_thread_id: None,
|
||||
timestamp: String::new(),
|
||||
@@ -2978,7 +2983,7 @@ impl Default for SessionMeta {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
||||
#[derive(Serialize, Debug, Clone, JsonSchema, TS)]
|
||||
pub struct SessionMetaLine {
|
||||
#[serde(flatten)]
|
||||
pub meta: SessionMeta,
|
||||
@@ -2986,6 +2991,35 @@ pub struct SessionMetaLine {
|
||||
pub git: Option<GitInfo>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SessionMetaLine {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct SessionMetaLineFields {
|
||||
#[serde(flatten)]
|
||||
meta: SessionMeta,
|
||||
git: Option<GitInfo>,
|
||||
}
|
||||
|
||||
let mut value = Value::deserialize(deserializer)?;
|
||||
let fields = value
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| D::Error::custom("session metadata must be an object"))?;
|
||||
if !fields.contains_key("session_id") {
|
||||
let thread_id = fields
|
||||
.get("id")
|
||||
.cloned()
|
||||
.ok_or_else(|| D::Error::missing_field("id"))?;
|
||||
fields.insert("session_id".to_string(), thread_id);
|
||||
}
|
||||
let SessionMetaLineFields { meta, git } =
|
||||
serde_json::from_value(value).map_err(D::Error::custom)?;
|
||||
Ok(Self { meta, git })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
|
||||
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
|
||||
pub enum RolloutItem {
|
||||
@@ -5390,6 +5424,7 @@ mod tests {
|
||||
let thread_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
|
||||
let older_meta = SessionMetaLine {
|
||||
meta: SessionMeta {
|
||||
session_id: thread_id.into(),
|
||||
id: thread_id,
|
||||
multi_agent_version: Some(MultiAgentVersion::V2),
|
||||
..Default::default()
|
||||
@@ -5398,6 +5433,7 @@ mod tests {
|
||||
};
|
||||
let newer_meta_without_version = SessionMetaLine {
|
||||
meta: SessionMeta {
|
||||
session_id: thread_id.into(),
|
||||
id: thread_id,
|
||||
multi_agent_version: None,
|
||||
..Default::default()
|
||||
|
||||
Reference in New Issue
Block a user