Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6193e7c
Exclude inherited Codex fork baselines
Finesssee Sep 22, 2026
f7d37e8
Model Codex parser modes explicitly
Finesssee Sep 22, 2026
340de7d
Persist inherited-only fork accounting
Finesssee Sep 22, 2026
1badfe8
Fix Codex fork accounting precedence
Finesssee Sep 22, 2026
d3be018
Fix Codex fork accounting test access
Finesssee Sep 22, 2026
f64861c
Fix Codex fork baseline provenance
Finesssee Sep 22, 2026
3521a23
Fix Codex parent baseline transition order
Finesssee Sep 22, 2026
f7d1ad9
Fix fork cache replacement test
Finesssee Sep 22, 2026
0735ff5
Reconcile inferred Codex forks in one scan
Finesssee Sep 22, 2026
361655e
Order Codex scan work by lineage
Finesssee Sep 22, 2026
b77e3c1
Merge remote-tracking branch 'origin/main' into codex/fix-pr-597-ci
Finesssee Sep 22, 2026
4d0b599
Fail closed on ambiguous Codex lineage
Finesssee Sep 22, 2026
9b2361a
Validate Codex lineage across refreshes
Finesssee Sep 22, 2026
0a2789e
Merge remote-tracking branch 'origin/main' into codex/fix-pr-597-ci
Finesssee Sep 22, 2026
4558dc2
Harden Codex fork lineage validation
Finesssee Sep 22, 2026
a7f685a
Consolidate Codex lineage validation graph
Finesssee Sep 22, 2026
ac88694
Reject late ordinal gaps in inferred forks
Finesssee Sep 22, 2026
775a4c4
Reuse Codex lineage plan per refresh
Finesssee Sep 23, 2026
bc1ac36
Initialize lineage planner in one branch expression
Finesssee Sep 23, 2026
c186e19
Prefer current Codex lineage metadata
Finesssee Sep 23, 2026
5d2d39e
Scope lineage invalidation to active dependencies
Finesssee Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions rust/src/core/jsonl_scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ pub(crate) struct CodexSessionMetadata {
pub lineage: CodexSessionLineage,
pub fork_timestamp: Option<String>,
pub history_base_thread_id: Option<String>,
pub is_subagent: bool,
pub subagent_history_start_ordinal: Option<i64>,
}

/// Running totals for Codex token counting
Expand All @@ -396,6 +398,10 @@ pub struct CodexForkAccountingState {
pub inherited_totals: Option<CodexTotals>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub remaining_inherited_totals: Option<CodexTotals>,
/// True when the child log itself supplied enough copied-prefix history to
/// establish the inherited baseline without consulting a parent cache row.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub locally_resolved: bool,
}

/// Snapshot of the last validated cost report, persisted so spend surfaces keep
Expand Down Expand Up @@ -456,6 +462,7 @@ pub struct CodexParseResult {
pub fork_baseline: Option<CodexTotals>,
/// Remaining inherited counters used when a fork emits last-only rows.
pub remaining_inherited_totals: Option<CodexTotals>,
pub fork_baseline_locally_resolved: bool,
}

/// A billable Codex token-count delta.
Expand Down
120 changes: 63 additions & 57 deletions rust/src/core/jsonl_scanner/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use helpers::{
BoundedJsonlLine, CODEX_JSONL_MAX_LINE_BYTES, nonempty_json_string, parse_rfc3339_timestamp,
read_bounded_jsonl_line, read_bounded_jsonl_line_until, session_meta_field,
};
use parser::CodexParserState;
use parser::{CodexParseMode, CodexParserState};

/// Persisted Codex cache schema version. Version 0 predates 64-bit totals;
/// version 1 can retain a terminal pause after treating a paginated v2
/// subagent's independent counters as an inherited fork. Version 3 adds
/// persisted paginated-fork accounting state. Rebuild older artifacts.
pub(crate) const CODEX_CACHE_SCHEMA_VERSION: u32 = 3;
/// persisted paginated-fork accounting state. Version 4 reparses copied-prefix
/// subagents with locally inferred component baselines. Rebuild older artifacts.
pub(crate) const CODEX_CACHE_SCHEMA_VERSION: u32 = 4;

/// Whether a persisted Codex cache artifact matches the current schema.
/// A mismatched artifact (e.g. a pre-64-bit cache from an older release) is
Expand Down Expand Up @@ -186,6 +187,12 @@ impl JsonlScanner {
.pointer("/source/subagent/thread_spawn")
.is_some_and(Value::is_object))
});
let is_subagent = payload.is_some_and(|value| {
value.get("thread_source").and_then(Value::as_str) == Some("subagent")
|| value
.pointer("/source/subagent/thread_spawn")
.is_some_and(Value::is_object)
});
let history_base_thread_id = payload
.and_then(|value| value.get("history_base"))
.filter(|value| value.is_object())
Expand Down Expand Up @@ -220,6 +227,10 @@ impl JsonlScanner {
payload.and_then(|value| nonempty_json_string(value.get("timestamp")))
}),
history_base_thread_id,
is_subagent,
subagent_history_start_ordinal: payload
.and_then(|value| value.get("subagent_history_start_ordinal"))
.and_then(Value::as_i64),
});
}

Expand Down Expand Up @@ -353,17 +364,16 @@ impl JsonlScanner {
Self::parse_codex_file_with_state_bounded_internal(
file_path,
range,
start_offset,
initial_model,
initial_totals,
previous_token_timestamp,
token_timestamps_monotonic,
cancel,
false,
false,
None,
None,
max_bytes_to_read,
CodexParseMode::Standard {
start_offset,
initial_model,
initial_totals,
previous_token_timestamp,
token_timestamps_monotonic,
},
)
}

Expand All @@ -389,17 +399,16 @@ impl JsonlScanner {
Self::parse_codex_file_with_state_bounded_internal(
file_path,
range,
start_offset,
initial_model,
initial_totals,
previous_token_timestamp,
token_timestamps_monotonic,
cancel,
false,
false,
None,
scan_target_size,
max_bytes_to_read,
CodexParseMode::Standard {
start_offset,
initial_model,
initial_totals,
previous_token_timestamp,
token_timestamps_monotonic,
},
)
}

Expand All @@ -420,17 +429,14 @@ impl JsonlScanner {
Self::parse_codex_file_with_state_bounded_internal(
file_path,
range,
0,
None,
Some(initial_totals),
None,
None,
cancel,
true,
false,
None,
None,
max_bytes_to_read,
CodexParseMode::ParentBaseline {
baseline: initial_totals,
paginated_continuation: false,
remaining_inherited_totals: None,
},
)
}

Expand Down Expand Up @@ -459,6 +465,26 @@ impl JsonlScanner {
)
}

pub(crate) fn parse_codex_file_with_inferred_fork_baseline(
file_path: &Path,
range: &CostUsageDayRange,
subagent_history_start_ordinal: Option<i64>,
cancel: Option<&AtomicBool>,
scan_target_size: Option<i64>,
max_bytes_to_read: Option<i64>,
) -> std::io::Result<CodexParseResult> {
Self::parse_codex_file_with_state_bounded_internal(
file_path,
range,
cancel,
scan_target_size,
max_bytes_to_read,
CodexParseMode::InferSubagent {
start_ordinal: subagent_history_start_ordinal,
},
)
}

/// Fork equivalent with persisted paginated-continuation accounting.
#[allow(
clippy::too_many_arguments,
Expand All @@ -477,38 +503,24 @@ impl JsonlScanner {
Self::parse_codex_file_with_state_bounded_internal(
file_path,
range,
0,
None,
Some(initial_totals),
None,
None,
cancel,
true,
paginated_continuation,
remaining_inherited_totals,
scan_target_size,
max_bytes_to_read,
CodexParseMode::ParentBaseline {
baseline: initial_totals,
paginated_continuation,
remaining_inherited_totals,
},
)
}

#[allow(
clippy::too_many_arguments,
reason = "resume state mirrors the persisted parser cache"
)]
fn parse_codex_file_with_state_bounded_internal(
file_path: &Path,
range: &CostUsageDayRange,
start_offset: i64,
initial_model: Option<String>,
initial_totals: Option<CodexTotals>,
previous_token_timestamp: Option<String>,
token_timestamps_monotonic: Option<bool>,
cancel: Option<&AtomicBool>,
fork_baseline_mode: bool,
paginated_continuation: bool,
remaining_inherited_totals: Option<CodexTotals>,
scan_target_size: Option<i64>,
max_bytes_to_read: Option<i64>,
mode: CodexParseMode,
) -> std::io::Result<CodexParseResult> {
let file = File::open(file_path)?;
// Session JSONL files are bounded by the cache budget; sizes fit i64.
Expand All @@ -518,7 +530,7 @@ impl JsonlScanner {
)]
let file_size = file.metadata()?.len() as i64;

let safe_start_offset = start_offset.clamp(0, file_size);
let safe_start_offset = mode.start_offset().clamp(0, file_size);
let requested_target_size = scan_target_size
.unwrap_or(file_size)
.max(safe_start_offset)
Expand All @@ -529,15 +541,7 @@ impl JsonlScanner {
reader.seek(SeekFrom::Start(safe_start_offset as u64))?;
}

let mut parser = CodexParserState::with_timestamp_state_and_fork_options(
initial_model,
initial_totals,
previous_token_timestamp,
token_timestamps_monotonic,
fork_baseline_mode,
paginated_continuation,
remaining_inherited_totals,
);
let mut parser = CodexParserState::from_mode(mode);
let mut parsed_bytes = safe_start_offset;
let mut committed_bytes = safe_start_offset;
let mut cancelled = false;
Expand Down Expand Up @@ -626,6 +630,7 @@ impl JsonlScanner {
};
let is_complete = !cancelled && !budget_exhausted && parsed_bytes >= effective_target_size;
let bytes_read = parsed_bytes.saturating_sub(safe_start_offset).max(0);
let fork_baseline_locally_resolved = parser.fork_baseline_locally_resolved();
Ok(CodexParseResult {
records: parser.records,
parsed_bytes,
Expand All @@ -644,6 +649,7 @@ impl JsonlScanner {
fork_baseline_ambiguous: parser.fork_baseline_ambiguous,
fork_baseline: parser.fork_baseline,
remaining_inherited_totals: parser.remaining_inherited_totals,
fork_baseline_locally_resolved,
})
}

Expand Down
Loading