From a6065d30f4446f2f048c596d0f993f3c9d5e8fab Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 26 Feb 2026 20:14:13 +0000 Subject: [PATCH] feat: add git info to memories (#12940) --- codex-rs/core/src/memories/storage.rs | 4 ++++ codex-rs/core/src/memories/tests.rs | 5 +++++ codex-rs/core/tests/suite/memories.rs | 7 ++++++ codex-rs/state/src/model/memories.rs | 4 ++++ codex-rs/state/src/runtime/memories.rs | 30 +++++++++++++++----------- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/memories/storage.rs b/codex-rs/core/src/memories/storage.rs index bd3eaa23a..1410c4e73 100644 --- a/codex-rs/core/src/memories/storage.rs +++ b/codex-rs/core/src/memories/storage.rs @@ -143,6 +143,9 @@ async fn write_rollout_summary_for_thread( writeln!(body, "rollout_path: {}", memory.rollout_path.display()) .map_err(rollout_summary_format_error)?; writeln!(body, "cwd: {}", memory.cwd.display()).map_err(rollout_summary_format_error)?; + if let Some(git_branch) = memory.git_branch.as_deref() { + writeln!(body, "git_branch: {git_branch}").map_err(rollout_summary_format_error)?; + } writeln!(body).map_err(rollout_summary_format_error)?; body.push_str(&memory.rollout_summary); body.push('\n'); @@ -273,6 +276,7 @@ mod tests { rollout_slug: rollout_slug.map(ToString::to_string), rollout_path: PathBuf::from("/tmp/rollout.jsonl"), cwd: PathBuf::from("/tmp/workspace"), + git_branch: None, generated_at: Utc.timestamp_opt(124, 0).single().expect("timestamp"), } } diff --git a/codex-rs/core/src/memories/tests.rs b/codex-rs/core/src/memories/tests.rs index a2498bc24..62cef7eae 100644 --- a/codex-rs/core/src/memories/tests.rs +++ b/codex-rs/core/src/memories/tests.rs @@ -88,6 +88,7 @@ async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only rollout_slug: None, rollout_path: PathBuf::from("/tmp/rollout-100.jsonl"), cwd: PathBuf::from("/tmp/workspace"), + git_branch: None, generated_at: Utc.timestamp_opt(101, 0).single().expect("timestamp"), }]; @@ -193,6 +194,7 @@ async fn sync_rollout_summaries_uses_timestamp_hash_and_sanitized_slug_filename( rollout_slug: Some("Unsafe Slug/With Spaces & Symbols + EXTRA_LONG_12345".to_string()), rollout_path: PathBuf::from("/tmp/rollout-200.jsonl"), cwd: PathBuf::from("/tmp/workspace"), + git_branch: Some("feature/memory-branch".to_string()), generated_at: Utc.timestamp_opt(201, 0).single().expect("timestamp"), }]; @@ -248,6 +250,7 @@ async fn sync_rollout_summaries_uses_timestamp_hash_and_sanitized_slug_filename( .expect("read rollout summary"); assert!(summary.contains(&format!("thread_id: {thread_id}"))); assert!(summary.contains("rollout_path: /tmp/rollout-200.jsonl")); + assert!(summary.contains("git_branch: feature/memory-branch")); assert!( !tokio::fs::try_exists(&stale_unslugged_path) .await @@ -294,6 +297,7 @@ task_outcome: success rollout_slug: Some("Unsafe Slug/With Spaces & Symbols + EXTRA_LONG_12345".to_string()), rollout_path: PathBuf::from("/tmp/rollout-200.jsonl"), cwd: PathBuf::from("/tmp/workspace"), + git_branch: None, generated_at: Utc.timestamp_opt(201, 0).single().expect("timestamp"), }]; @@ -378,6 +382,7 @@ mod phase2 { rollout_slug: None, rollout_path: PathBuf::from("/tmp/rollout-summary.jsonl"), cwd: PathBuf::from("/tmp/workspace"), + git_branch: None, generated_at: chrono::DateTime::::from_timestamp(source_updated_at + 1, 0) .expect("valid generated_at timestamp"), } diff --git a/codex-rs/core/tests/suite/memories.rs b/codex-rs/core/tests/suite/memories.rs index 0578f5b96..8f96178e8 100644 --- a/codex-rs/core/tests/suite/memories.rs +++ b/codex-rs/core/tests/suite/memories.rs @@ -83,6 +83,7 @@ async fn memories_startup_phase2_tracks_added_and_removed_inputs_across_runs() - let rollout_summaries = read_rollout_summary_bodies(&memory_root).await?; assert_eq!(rollout_summaries.len(), 1); assert!(rollout_summaries[0].contains("rollout summary A")); + assert!(rollout_summaries[0].contains("git_branch: branch-rollout-a")); shutdown_test_codex(&first).await?; @@ -141,6 +142,11 @@ async fn memories_startup_phase2_tracks_added_and_removed_inputs_across_runs() - .iter() .any(|summary| summary.contains("rollout summary B")) ); + assert!( + rollout_summaries + .iter() + .any(|summary| summary.contains("git_branch: branch-rollout-b")) + ); assert!( rollout_summaries .iter() @@ -185,6 +191,7 @@ async fn seed_stage1_output( ); metadata_builder.cwd = codex_home.join(format!("workspace-{rollout_slug}")); metadata_builder.model_provider = Some("test-provider".to_string()); + metadata_builder.git_branch = Some(format!("branch-{rollout_slug}")); let metadata = metadata_builder.build("test-provider"); db.upsert_thread(&metadata).await?; diff --git a/codex-rs/state/src/model/memories.rs b/codex-rs/state/src/model/memories.rs index 6c88d7360..0e663bf90 100644 --- a/codex-rs/state/src/model/memories.rs +++ b/codex-rs/state/src/model/memories.rs @@ -18,6 +18,7 @@ pub struct Stage1Output { pub rollout_summary: String, pub rollout_slug: Option, pub cwd: PathBuf, + pub git_branch: Option, pub generated_at: DateTime, } @@ -45,6 +46,7 @@ pub(crate) struct Stage1OutputRow { rollout_summary: String, rollout_slug: Option, cwd: String, + git_branch: Option, generated_at: i64, } @@ -58,6 +60,7 @@ impl Stage1OutputRow { rollout_summary: row.try_get("rollout_summary")?, rollout_slug: row.try_get("rollout_slug")?, cwd: row.try_get("cwd")?, + git_branch: row.try_get("git_branch")?, generated_at: row.try_get("generated_at")?, }) } @@ -75,6 +78,7 @@ impl TryFrom for Stage1Output { rollout_summary: row.rollout_summary, rollout_slug: row.rollout_slug, cwd: PathBuf::from(row.cwd), + git_branch: row.git_branch, generated_at: epoch_seconds_to_datetime(row.generated_at)?, }) } diff --git a/codex-rs/state/src/runtime/memories.rs b/codex-rs/state/src/runtime/memories.rs index 6803742b8..5ebb2be88 100644 --- a/codex-rs/state/src/runtime/memories.rs +++ b/codex-rs/state/src/runtime/memories.rs @@ -224,7 +224,7 @@ LEFT JOIN jobs /// /// Query behavior: /// - filters out rows where both `raw_memory` and `rollout_summary` are blank - /// - joins `threads` to include thread `cwd` and `rollout_path` + /// - joins `threads` to include thread `cwd`, `rollout_path`, and `git_branch` /// - orders by `source_updated_at DESC, thread_id DESC` /// - applies `LIMIT n` pub async fn list_stage1_outputs_for_global( @@ -244,8 +244,9 @@ SELECT so.raw_memory, so.rollout_summary, so.rollout_slug, - so.generated_at - , COALESCE(t.cwd, '') AS cwd + so.generated_at, + COALESCE(t.cwd, '') AS cwd, + t.git_branch AS git_branch FROM stage1_outputs AS so LEFT JOIN threads AS t ON t.id = so.thread_id @@ -301,9 +302,10 @@ SELECT so.rollout_summary, so.rollout_slug, so.generated_at, + COALESCE(t.cwd, '') AS cwd, + t.git_branch AS git_branch, so.selected_for_phase2, - so.selected_for_phase2_source_updated_at, - COALESCE(t.cwd, '') AS cwd + so.selected_for_phase2_source_updated_at FROM stage1_outputs AS so LEFT JOIN threads AS t ON t.id = so.thread_id @@ -352,9 +354,10 @@ SELECT so.source_updated_at, so.raw_memory, so.rollout_summary, - so.rollout_slug - , so.generated_at - , COALESCE(t.cwd, '') AS cwd + so.rollout_slug, + so.generated_at, + COALESCE(t.cwd, '') AS cwd, + t.git_branch AS git_branch FROM stage1_outputs AS so LEFT JOIN threads AS t ON t.id = so.thread_id @@ -2215,12 +2218,11 @@ WHERE kind = 'memory_stage1' )) .await .expect("upsert thread a"); + let mut metadata_b = + test_thread_metadata(&codex_home, thread_id_b, codex_home.join("workspace-b")); + metadata_b.git_branch = Some("feature/stage1-b".to_string()); runtime - .upsert_thread(&test_thread_metadata( - &codex_home, - thread_id_b, - codex_home.join("workspace-b"), - )) + .upsert_thread(&metadata_b) .await .expect("upsert thread b"); @@ -2279,10 +2281,12 @@ WHERE kind = 'memory_stage1' assert_eq!(outputs[0].rollout_summary, "summary b"); assert_eq!(outputs[0].rollout_slug.as_deref(), Some("rollout-b")); assert_eq!(outputs[0].cwd, codex_home.join("workspace-b")); + assert_eq!(outputs[0].git_branch.as_deref(), Some("feature/stage1-b")); assert_eq!(outputs[1].thread_id, thread_id_a); assert_eq!(outputs[1].rollout_summary, "summary a"); assert_eq!(outputs[1].rollout_slug, None); assert_eq!(outputs[1].cwd, codex_home.join("workspace-a")); + assert_eq!(outputs[1].git_branch, None); let _ = tokio::fs::remove_dir_all(codex_home).await; }