Skip to content

fix(codex): stream large session files - #1123

Closed
pretendhigh wants to merge 1 commit into
ccusage:mainfrom
pretendhigh:fix/codex-stream-large-sessions
Closed

pretendhigh wants to merge 1 commit into
ccusage:mainfrom
pretendhigh:fix/codex-stream-large-sessions

Conversation

@pretendhigh

@pretendhigh pretendhigh commented May 22, 2026 •

Copy link
Copy Markdown

Streams Codex JSONL session files through a buffered line reader instead of reading each file fully into memory. This keeps ccusage codex daily usable when local Codex sessions grow to multi-GB files while preserving the existing token-count parsing, model tracking, and aggregation behavior.

Testing:

  • cargo fmt --manifest-path rust/Cargo.toml --all --check
  • cargo test --manifest-path rust/Cargo.toml -p ccusage codex_loader::tests::parses_codex_usage_from_streaming_reader
  • cargo test --manifest-path rust/Cargo.toml --workspace
  • cargo clippy --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings
  • CODEX_HOME=/private/tmp/ccusage-codex-large-fixture rust/target/release/ccusage codex daily --offline --json

Summary by cubic

Stream Codex JSONL session files with a buffered line reader instead of loading whole files into memory. This keeps ccusage codex daily usable with multi‑GB session files while preserving token parsing, model tracking, and aggregation.

  • Bug Fixes
    • Stream lines with BufRead/BufReader to keep memory usage low.
    • Preserve model detection and fallback timestamp behavior.
    • Introduce visit_codex_session_reader and a streaming test to verify parsing.

Written for commit 0035388. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Refactor

    • Improved session parsing to stream data rather than loading files fully, reducing memory use and improving performance for large sessions; token usage and exec events are now processed more reliably.
  • Tests

    • Added unit tests to validate the new streaming parsing behavior and event emission.

Review Change Stack

Read Codex JSONL sessions through a buffered line reader instead of loading each file into memory at once.

This keeps codex reports usable when a session file grows to multiple gigabytes while preserving the existing line-level parsing, model tracking, and usage aggregation behavior.
@github-actions

Copy link
Copy Markdown
Contributor

This PR was auto-closed. Only contributors approved with lgtm can open PRs. Open an issue first.

Maintainers review auto-closed issues and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md may not be reopened or receive a reply.

If a maintainer replies lgtmi, your future issues will stay open. If a maintainer replies lgtm, your future issues and PRs will stay open.

See CONTRIBUTING.md.

@github-actions github-actions Bot closed this May 22, 2026
@coderabbitai

coderabbitai Bot commented May 22, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8be39a6f-5ac2-477d-bd24-8fabf91bfbc4

📥 Commits

Reviewing files that changed from the base of the PR and between 78b93c2 and 0035388.

📒 Files selected for processing (1)
  • rust/crates/ccusage/src/codex_loader.rs

📝 Walkthrough

Walkthrough

Refactored Codex session file parsing to use streaming BufRead reader instead of loading files entirely into memory. Updated imports to support streaming, reworked the main parsing function to delegate JSONL line processing to a new helper, adjusted callback invocations for reference semantics, and added a unit test validating streaming JSONL parsing via in-memory cursor.

Changes

Codex Streaming Parser

Layer / File(s) Summary
Streaming reader implementation
rust/crates/ccusage/src/codex_loader.rs
Imports updated to include BufRead and BufReader. The visit_codex_session_file function now opens files and delegates per-line JSONL processing to a new visit_codex_session_reader helper that iterates content via read_until over a BufRead, computing and passing fallback timestamps to the visitor. The add_codex_exec_event call was adjusted to pass the fallback timestamp with correct reference semantics.
Streaming reader validation test
rust/crates/ccusage/src/codex_loader.rs
Unit test verifying visit_codex_session_reader parses token usage and model context from streaming JSONL input provided via an in-memory Cursor, validating the streaming parsing path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ryoppippi/ccusage#1036: Modifies codex_loader.rs to refactor/extend Codex JSONL parsing for exec/usage events with timestamp and model context extraction, directly related to this PR's streaming parsing changes.
  • ryoppippi/ccusage#706: Also switches session/data loading to line-by-line streaming and updates per-line parsing flows, analogous streaming parsing work in another language/runtime.

Suggested labels

enhancement

Suggested reviewers

  • ryoppippi

Poem

🐇 I hop through lines, one by one,
Streams now hum beneath the sun.
BufRead sifts each JSON tune,
Tests ensure no parse goes bloon.
thump ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pretendhigh

Copy link
Copy Markdown
Author

@coderabbitai review
@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review
@cubic-dev-ai review

@pretendhigh I have started the AI code review. It will take a few minutes to complete.

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR details
gh pr view 1123 --repo ryoppippi/ccusage --json title,body,files,additions,deletions,changedFiles

# Get the diff
gh pr diff 1123 --repo ryoppippi/ccusage

Length of output: 6148


🏁 Script executed:

#!/bin/bash
# Get the full file from the PR branch
gh api repos/ryoppippi/ccusage/contents/rust/crates/ccusage/src/codex_loader.rs \
  --jq '.content' | base64 -d

echo "---BRANCH INFO---"
gh pr view 1123 --repo ryoppippi/ccusage --json headRefName,headRepositoryOwner

Length of output: 21859


@pretendhigh Great PR — the streaming refactor is clean and the approach is solid. Here's the review:


Review of rust/crates/ccusage/src/codex_loader.rs

✅ What's done well

  • Clean separation: visit_codex_session_file stays as the public API, while the new private visit_codex_session_reader(impl BufRead, ...) holds the testable logic. This pattern makes unit testing straightforward with std::io::Cursor.
  • Buffer reuse: Declaring let mut line = Vec::new() once outside the loop and calling line.clear() per iteration avoids repeated allocations — good for hot paths over large files.
  • read_until + serde_json compatibility: read_until(b'\n', &mut line) includes the trailing \n in the buffer, but serde_json::from_slice tolerates trailing whitespace and memmem::find is unaffected, so this is correct.
  • Parallel path automatically benefits: read_codex_session_file delegates to visit_codex_session_file unchanged, so the multi-threaded path in read_codex_session_files_parallel streams too — no extra work needed.
  • Imports are consistent: byte_lines is correctly removed since read_codex_session_file itself no longer uses it directly.
  • mut placement: Correctly moved from the visit parameter in visit_codex_session_file (which only passes it through) to visit_codex_session_reader (which actually calls it).

⚠️ Minor observation: unbounded line buffer on malformed input

read_until(b'\n', ...) will read until a \n or EOF. If a session file is malformed and contains no newlines at all (or has a single multi-GB JSON object on one line), the buffer will grow to hold the entire file contents — the same worst case as the original fs::read. For the known Codex JSONL format this is unlikely, but it's worth noting. A defensive cap like:

loop {
    line.clear();
    let bytes_read = reader.read_until(b'\n', &mut line)?;
    if bytes_read == 0 {
        break;
    }
    // Optional: skip pathologically large lines
    if line.len() > MAX_LINE_SIZE {
        continue;
    }
    // ...
}

…could protect against degenerate inputs, though it may be overkill given the real-world file format.

✅ Error handling

The ? on read_until propagates IO errors through visit_codex_session_reader → visit_codex_session_file → read_codex_session_file (which discards via let _ = ...). This is actually a slight improvement over the original behaviour, where any mid-read error was silently swallowed along with the rest of the file.

✅ Test

parses_codex_usage_from_streaming_reader is comprehensive — it covers model extraction via turn_context, token parsing via event_msg/token_count, session ID derivation, and all individual token fields. Using std::io::Cursor to exercise the reader abstraction is exactly the right approach.


Overall: The logic is correct, the abstraction is clean, and the test covers the key path well. The line-buffer growth observation is minor and the existing format makes it a non-issue in practice. LGTM. 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@ryoppippi

Copy link
Copy Markdown
Member

Historical audit: this pull request was auto-closed by the legacy contributor gate. That closure did not assess technical importance.

Audit result: needs review. The current state does not prove resolution, but a fresh technical or product check is required before deciding whether the underlying request is still relevant. This closed PR will not be revived as-is; create a new PR only after reviewing the related issue and current main.

@ryoppippi ryoppippi added the triage:needs-review Requires a fresh technical or product decision. label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage:needs-review Requires a fresh technical or product decision.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants