use claudectl_core::agent::codex;
const WINDOW_SECS: u64 = 90 * 24 * 3600;
fn now_secs() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[test]
fn real_rollouts_still_parse() {
let root = codex::sessions_root();
if !root.is_dir() {
eprintln!("skipped: {} does not exist", root.display());
return;
}
let summaries = codex::scan(now_secs(), WINDOW_SECS);
if summaries.is_empty() {
eprintln!("skipped: no rollouts under {}", root.display());
return;
}
eprintln!(
"parsed {} rollouts from {}",
summaries.len(),
root.display()
);
let mut with_cwd = 0;
let mut with_window = 0;
let mut with_tokens = 0;
let mut with_tool = 0;
for s in &summaries {
assert!(
!s.session_id.is_empty(),
"every parsed rollout must have an id; {} did not",
s.path.display()
);
assert!(
s.last_event_secs >= s.started_at_secs,
"last event precedes first in {}",
s.path.display()
);
if !s.cwd.is_empty() {
with_cwd += 1;
}
if s.context_window.is_some() {
with_window += 1;
}
if s.total_tokens > 0 {
with_tokens += 1;
}
if s.last_tool.is_some() {
with_tool += 1;
}
}
let n = summaries.len();
eprintln!(" cwd: {with_cwd}/{n} context_window: {with_window}/{n}");
eprintln!(" tokens: {with_tokens}/{n} last_tool: {with_tool}/{n}");
assert!(
with_cwd > 0,
"no rollout yielded a cwd — `session_meta.cwd` may have been renamed"
);
assert!(
with_tokens > 0,
"no rollout yielded token usage — `token_count.total_token_usage` may have moved"
);
}
#[test]
fn real_tool_names_normalize_to_the_canonical_vocabulary() {
use claudectl_core::agent::vocab;
if !codex::sessions_root().is_dir() {
eprintln!("skipped: no ~/.codex/sessions");
return;
}
let summaries = codex::scan(now_secs(), WINDOW_SECS);
let tools: std::collections::BTreeSet<String> = summaries
.iter()
.filter_map(|s| s.last_tool.clone())
.collect();
if tools.is_empty() {
eprintln!("skipped: no tool calls in any rollout");
return;
}
eprintln!("canonical last-tool names seen: {tools:?}");
for tool in &tools {
let again = vocab::canonical_tool(vocab::CLAUDE_CODE, None, tool, None);
assert_eq!(
&again, tool,
"a canonical name must survive re-normalization; '{tool}' did not"
);
if !tool.starts_with("codex:") && !tool.starts_with("mcp__") {
assert!(
!tool.contains('/'),
"a mapped name should carry no namespace separator: {tool}"
);
}
}
}
#[test]
fn real_rollouts_become_sessions_that_cannot_be_acted_on() {
use claudectl_core::session::ClaudeSession;
if !codex::sessions_root().is_dir() {
eprintln!("skipped: no ~/.codex/sessions");
return;
}
let summaries = codex::scan(now_secs(), WINDOW_SECS);
if summaries.is_empty() {
eprintln!("skipped: no rollouts");
return;
}
for summary in summaries.iter().take(50) {
let session = ClaudeSession::from_codex_rollout(summary);
assert!(
!session.is_actionable(),
"a real Codex session must never be actionable: {}",
session.session_id
);
assert_eq!(session.pid, 0);
assert!(!session.has_known_cost());
assert!(
session.jsonl_path.is_some(),
"the rollout is the transcript, so the path must be set"
);
}
}