Skip to main content

dynamo_bench/coding/codex/
discovery.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::coding::common::{dedupe_paths, expand_user_path, home_dir};
5use anyhow::{Result, bail};
6use std::fs;
7use std::path::{Path, PathBuf};
8
9/// Returns `$CODEX_HOME`, or `~/.codex` when it is unset.
10pub fn codex_home() -> Option<PathBuf> {
11    match std::env::var_os("CODEX_HOME") {
12        Some(home) if !home.is_empty() => Some(PathBuf::from(home)),
13        _ => home_dir().map(|home| home.join(".codex")),
14    }
15}
16
17/// Finds Codex rollout files under the explicit inputs, or under the Codex home.
18///
19/// Codex moves rollouts from `sessions` into `archived_sessions` while it runs, so default
20/// discovery scans both to keep agent trees complete.
21pub fn discover_rollout_files(explicit_inputs: &[String]) -> Result<Vec<PathBuf>> {
22    if explicit_inputs.is_empty() {
23        let Some(codex_home) = codex_home() else {
24            bail!("could not resolve CODEX_HOME or HOME for Codex rollout discovery");
25        };
26        let mut discovered = scan_rollout_dir(&codex_home.join("sessions"))?;
27        discovered.extend(scan_rollout_dir(&codex_home.join("archived_sessions"))?);
28        return Ok(discovered);
29    }
30
31    let mut discovered = Vec::new();
32    for raw_path in explicit_inputs {
33        let input_path = expand_user_path(raw_path);
34        if input_path.is_file() {
35            if !is_rollout_path(&input_path) {
36                bail!("not a Codex rollout file: {}", input_path.display());
37            }
38            discovered.push(input_path);
39            continue;
40        }
41        if !input_path.is_dir() {
42            bail!("input path does not exist: {}", input_path.display());
43        }
44        let hits = scan_rollout_dir(&input_path)?;
45        if hits.is_empty() {
46            bail!(
47                "no Codex rollout files found under {}",
48                input_path.display()
49            );
50        }
51        discovered.extend(hits);
52    }
53    Ok(dedupe_paths(discovered))
54}
55
56fn scan_rollout_dir(root: &Path) -> Result<Vec<PathBuf>> {
57    if !root.exists() {
58        return Ok(Vec::new());
59    }
60    let mut pending = vec![root.to_path_buf()];
61    let mut discovered = Vec::new();
62    while let Some(directory) = pending.pop() {
63        for entry in fs::read_dir(&directory)? {
64            let entry = entry?;
65            let path = entry.path();
66            if entry.file_type()?.is_dir() {
67                pending.push(path);
68            } else if is_rollout_path(&path) {
69                discovered.push(path);
70            }
71        }
72    }
73    discovered.sort();
74    Ok(discovered)
75}
76
77fn is_rollout_path(path: &Path) -> bool {
78    let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
79        return false;
80    };
81    name.starts_with("rollout-") && name.ends_with(".jsonl")
82}
83
84#[cfg(test)]
85mod tests {
86    use super::discover_rollout_files;
87    use std::fs;
88    use tempfile::TempDir;
89
90    #[test]
91    fn discovers_nested_rollouts_and_rejects_other_files() {
92        let temp = TempDir::new().unwrap();
93        let day = temp.path().join("2026").join("09").join("30");
94        fs::create_dir_all(&day).unwrap();
95        fs::write(day.join("rollout-a.jsonl"), "").unwrap();
96        fs::write(day.join("notes.jsonl"), "").unwrap();
97
98        let found = discover_rollout_files(&[temp.path().display().to_string()]).unwrap();
99        assert_eq!(found, [day.join("rollout-a.jsonl").canonicalize().unwrap()]);
100        assert!(discover_rollout_files(&[day.join("notes.jsonl").display().to_string()]).is_err());
101    }
102}