Skip to content

fix: make Codex cost catch-up append-linear - #2726

Closed
xx205 wants to merge 4 commits into
steipete:mainfrom
xx205:fix/codex-catchup-linear-indexing
Closed

fix: make Codex cost catch-up append-linear#2726
xx205 wants to merge 4 commits into
steipete:mainfrom
xx205:fix/codex-catchup-linear-indexing

Conversation

@xx205

@xx205 xx205 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Make bounded Codex usage catch-up append-linear while preserving the accounting and fork-safety guarantees added by #2452, #2525, and #2648.

This change:

  • persists token accumulator state and append-only usage events in a SQLite sidecar;
  • publishes immutable usage-row generations through the existing JSON cache authority;
  • validates append reuse with file identity, ctime, size, and prefix anchors;
  • preserves fail-closed deferred replay for unresolved forks and subagents;
  • consumes published row references and suffix rows instead of rebuilding all historical workspace rows on every pass;
  • retires replaced SQLite row generations from the moment they become unreferenced, so readers that selected the previous JSON publication retain a safe grace window;
  • serializes app, account, and CLI cache writers with a cross-process lock; and
  • keeps Finish Now progress monotonic while retrying transient contention or no-progress passes.

The existing automatic limits remain unchanged: at most 256 MiB per file and 512 MiB per pass, followed by the existing background duty cycle.

Problem

#2452 bounded scans of very large Codex session corpora. #2525 restored accurate fork accounting during bounded catch-up, and #2648 preserved ordinary-fork progress when a session file was appended.

The remaining slowdown came from older cache architecture shared by those paths: after reading each new bounded JSONL suffix, the scanner rebuilt increasingly large in-memory token snapshots, checkpoints, fingerprints, and workspace usage rows from all accumulated history. Work therefore grew with every pass and approached quadratic behavior. Finish Now exposed that behavior by running passes back-to-back; it did not create it.

There were also narrow resume gaps around metadata-free files, predecessor producer generations, and bounded metadata parsing. Those could force redundant work or make an otherwise reusable cache generation unavailable.

Implementation

Append-linear token index

The token sidecar stores the accumulator state needed at the next byte offset plus append-only usage events. A pass reads and parses only the new JSONL suffix when the stored prefix is still valid.

Append reuse is accepted only after validating the physical file identity, ctime/size relationship, and sampled prefix anchors. Ambiguous ownership or content changes still use the conservative byte-zero reconciliation path.

Transactional usage-row publication

The workspace sidecar stores immutable usage-row generations. Compact JSON cache state remains the publication authority, so a crash may leave SQLite ahead of JSON but cannot expose an unpublished partial generation.

Per-entry producer keys allow compatible cached rows to survive a global producer migration. The first append after migration imports the predecessor generation and then processes only the JSONL suffix.

Reader-safe generation retirement

Garbage collection now records when a generation first becomes absent from the complete published JSON reference set. It deletes that generation only after it has remained unreferenced for the full 24-hour grace period, rather than measuring age from generation creation time.

The SQLite schema migrates from v1 to v2 in place while the refresh lease is held. A generation selected by a concurrent reader therefore remains loadable across a replacement publication, and republishing a retired generation clears its prior retirement deadline before the next sweep.

Fork and subagent safety

Resolved forks reuse validated inherited baselines. Missing-parent forks and subagent buffers remain deferred and fail closed: the scanner does not publish incomplete suffix accounting and does not silently fall back to counting inherited cumulative totals as new usage.

Finish Now coordination

Finish Now bypasses the normal scanner debounce, retries transient lock contention and no-progress passes, and identifies metadata-only progress without resetting the visible byte count. Cross-process locking also provides a stable cache-clear barrier.

Privacy-safe representative validation

I ran Finish Now-style bounded catch-up against an isolated projection of real local Codex JSONL data. The projection was copied before the run, and the original session files were not modified.

Check Result
Representative corpus approximately 3 GiB across 10–99 files
Covered shapes current session family, large file, resolved fork, missing parent, subagent, and small files
Bounded Finish Now catch-up 38 passes in 79 s
Independent reference scan 6 passes in 78 s
Accounting parity bounded result = persisted-restart result = independent reference result
Compact 478 MiB large-prefix case bounded: 2 passes / 17 s; one-shot: 2 passes / 18 s; exact result match
Sustained throughput first quartile 38 MiB/s; last quartile 39 MiB/s; ratio 1.04 across 33 samples
Stable EOF replay zero JSON body work, zero usage-row work, and unchanged sidecars
Peak derived storage SQLite 16 MiB; JSON cache below 1 MiB

No paths, usernames, session IDs, dates, model names, token totals, prompts, responses, or JSONL content are included in this proof.

The representative run validates the core indexing and publication design. After the final narrow edge-case fixes, the exact submitted tree was also checked with deterministic regressions for:

  • metadata-free partial files using 1 KiB slices: 12 monotonic passes to EOF, followed by a zero-work replay;
  • predecessor SQLite row generations: stable reads import with zero JSON body work, the first append is suffix-only and matches a cold control, and the following pass does zero work;
  • nil-session rowless EOF state: current generations do zero work and predecessor generations migrate once; and
  • oversized leading metadata records plus bounded reads ending exactly at immutable EOF.

The reviewer-reported reader/GC interleaving also has a deterministic, privacy-safe before/after proof using two row-store instances sharing a synthetic database:

Step Before fix After fix
Reader selects generation A; writer publishes B and immediately sweeps A deleted (deletedGenerationCount = 1) A retired but retained (deletedGenerationCount = 0)
Reader imports its already-selected A reference needsRebuild exact synthetic row match
Sweep after A has been unreferenced for more than 24 hours already missing A deleted (deletedGenerationCount = 1)

Additional regressions verify that v1-to-v2 migration preserves published rows and that republishing a retired generation restarts its grace window. All identifiers and token values in these tests are synthetic.

Verification

  • swift test --jobs 2 list prebuild, followed by the repository's isolated sharded test runner: 834 test selections in 70 groups; 70/70 passed on the first attempt; 0 failures, retries, or timeouts; 806.8 s total.
  • 142 focused row-store, cache, workspace-sidecar, scanner, and token-index regression tests passed.
  • ProviderArchitectureGatekeeperTests: 38/38 passed, including the exact-anchor and provider-reference fingerprint scan added on current main.
  • make check: portable repository checks, parser hash, SwiftFormat, and strict SwiftLint all passed; 0 SwiftLint violations across 1,821 files.
  • git diff --check passed.
  • Compile and test concurrency was capped at two jobs.

Scope and limitations

  • A cold corpus must still read every relevant JSONL byte once.
  • Automatic byte limits and duty-cycle behavior are unchanged.
  • This does not change token accounting or pricing formulas.
  • The representative proof intentionally uses approximately 3 GiB rather than retaining or publishing a full 42+ GiB local corpus.
  • Ambiguous duplicate physical ownership still chooses correctness over speed and performs byte-zero reconciliation.
  • If SQLite is transiently unavailable, publication is deferred instead of publishing a partial suffix.
  • The progress denominator may legitimately grow when an active file is appended or a previously missing parent becomes discoverable.

No UI strings or layouts changed, so screenshots are not applicable.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dce98a9bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +6915 to +6918
_ = try usageRowStore.garbageCollect(
publishedGenerationIDs: publishedGenerationIDs,
gracePeriod: 24 * 60 * 60,
now: maintenanceNow)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep retired row generations until readers release them

When a Workspace refresh loads the published JSON cache and another app/CLI process subsequently replaces a generation, this sweep can immediately delete that old generation if its created_at_ms is over 24 hours old. CodexLocalProjectUsageIndexer releases the refresh lease before loading the cache and later opens the row store during synchronizeSources, so it can still hold the old JSON reference when this deletion occurs; the ensuing load/loadSuffix reports needsRebuild and aborts an otherwise valid Workspace refresh. Base the grace period on when the generation became unreferenced, or keep readers under the same lease through sidecar import.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 7, 2026
@clawsweeper

clawsweeper Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed August 7, 2026, 7:00 AM ET / 11:00 UTC.

ClawSweeper review

What this changes

This PR makes bounded Codex cost catch-up append-linear by storing token and usage-row history in SQLite sidecars while retaining JSON as the published cache authority.

Merge readiness

⚠️ Ready for maintainer review - 3 items remain

Keep open for maintainer review: current main does not contain this sidecar migration, and no line-level correctness blocker was found in the final head; its persistent-cache and cross-process-lock scope warrants an explicit merge-risk decision.

Priority: P2
Reviewed head: 1946578513493f8d06298aca0bf5bccfbd1c035b
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) Strong real-behavior evidence and extensive targeted regressions support a large but review-intensive cache migration.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The PR body provides privacy-safe after-fix real-corpus catch-up metrics plus final-head deterministic recovery evidence; the downloaded screenshots document the earlier UI flow and are not relied on as proof for this internal cache change.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The PR body provides privacy-safe after-fix real-corpus catch-up metrics plus final-head deterministic recovery evidence; the downloaded screenshots document the earlier UI flow and are not relied on as proof for this internal cache change.
Evidence reviewed 5 items Current main does not supersede the branch: The only current-main changes after the PR base are release artifacts, while the scanner, sidecar, lock, and regression-test changes remain unique to this branch.
Reader-safe retirement implementation: The final head marks generations unreferenced only after they disappear from the complete published JSON reference set and deletes them after the grace period while the refresh lease is held.
Earlier review concern is covered by regression evidence: The final head includes a deterministic test for a reader selecting a generation before a writer publishes its replacement and performs garbage collection.
Findings None None.
Security None None.

How this fits together

CodexBar scans local Codex JSONL sessions to derive local cost history for Usage & Spend and the CLI. This change sits between bounded session parsing and those consumers, replacing repeated in-memory reconstruction with persisted, validated cache state.

flowchart LR
A[Codex JSONL sessions] --> B[Bounded cost scanner]
B --> C[Append and source validation]
C --> D[SQLite token index]
C --> E[SQLite usage-row store]
D --> F[Published JSON cache]
E --> F
F --> G[Usage & Spend]
F --> H[CLI cache commands]
Loading

Decision needed

Question Recommendation
Should this 11k-line persisted-cache and locking migration be accepted for the next release after an owner reviews its upgrade and recovery boundary? Approve after migration review: Accept the migration after confirming the final-head coverage for pre-sidecar cache adoption, reader retention, lock contention, and cache clearing.

Why: Tests and real-corpus evidence support the implementation, but accepting a new on-disk storage protocol and its release timing is a product-risk decision rather than a mechanical code repair.

Before merge

  • Resolve merge risk (P1) - This introduces a large persisted-cache migration and cross-process locking protocol; an upgrade edge case could defer local cost refreshes or require a safe cache rebuild for existing users.
  • Resolve merge risk (P1) - The 24-hour reader-retention window and cache-clear barrier are covered by focused regressions, but release acceptance of this storage-protocol expansion remains a maintainer judgment.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production versus test growth production +11,157/-893; tests +10,047/-155 The production growth implements a persistent cache protocol and is accompanied by substantial focused regression coverage, so upgrade review matters more than ordinary feature review.
Affected files 38 files changed The change crosses scanner, cache, app, CLI, and test surfaces rather than being a localized parser adjustment.

Merge-risk options

Maintainer options:

  1. Approve with upgrade ownership (recommended)
    Merge after a maintainer accepts the tested cache migration, lock contention, and cache-clear recovery behavior for existing installs.
  2. Stage the persistence change
    Pause this branch if the release should not take an 11k-line local-storage protocol migration in one change.

Technical review

Best possible solution:

Land only if an owner accepts the SQLite-sidecar upgrade boundary with JSON publication remaining authoritative and the tested migration, contention, and cache-clear recovery behavior preserved.

Do we have a high-confidence way to reproduce the issue?

Yes. A growing Codex JSONL corpus exercised through repeated bounded catch-up passes exposes the historical rebuild path; the PR also supplies a privacy-safe real-corpus run and deterministic regression coverage for the final head.

Is this the best way to solve the issue?

Yes, conditionally. Persisting validated append state while retaining JSON as the atomic publication authority is the narrow maintainable way to remove repeated historical reconstruction, subject to maintainer acceptance of the migration risk.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 22b24b885693.

Labels

Label justifications:

  • P2: This is a bounded local-cost reliability and performance improvement with meaningful but non-emergency user impact.
  • merge-risk: 🚨 compatibility: Existing JSON cache state is migrated to SQLite-backed sidecars and must remain safely reusable or rebuildable across upgrades.
  • merge-risk: 🚨 availability: Refresh and cache-clear operations now coordinate through a cross-process lock, so a protocol defect could defer local cost refreshes.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body provides privacy-safe after-fix real-corpus catch-up metrics plus final-head deterministic recovery evidence; the downloaded screenshots document the earlier UI flow and are not relied on as proof for this internal cache change.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides privacy-safe after-fix real-corpus catch-up metrics plus final-head deterministic recovery evidence; the downloaded screenshots document the earlier UI flow and are not relied on as proof for this internal cache change.

Evidence

What I checked:

  • Current main does not supersede the branch: The only current-main changes after the PR base are release artifacts, while the scanner, sidecar, lock, and regression-test changes remain unique to this branch. (22b24b885693)
  • Reader-safe retirement implementation: The final head marks generations unreferenced only after they disappear from the complete published JSON reference set and deletes them after the grace period while the refresh lease is held. (Sources/CodexBarCore/Vendored/CostUsage/CostUsageCodexUsageRowStore.swift:992, 194657851349)
  • Earlier review concern is covered by regression evidence: The final head includes a deterministic test for a reader selecting a generation before a writer publishes its replacement and performs garbage collection. (Tests/CodexBarTests/CostUsageCodexUsageRowStoreTests.swift:482, 194657851349)
  • Feature-history routing: Merged bounded-scan and fork-resume work establishes the current scanner lineage; the final branch follows those paths rather than introducing a parallel provider flow. (Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift:6576, 4f99e6aba8c3)
  • Submitted validation and checks: The PR body reports an isolated real-corpus bounded catch-up run, deterministic edge-case regressions on the submitted tree, and all listed hosted checks are successful. (194657851349)

Likely related people:

  • xx205: Authored the merged fork-accuracy and append-resume work that this PR explicitly extends. (role: recent area contributor; confidence: high; commits: 2920019bc16d, 4f99e6aba8c3; files: Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift, Sources/CodexBar/UsageStore+CodexCostCatchUp.swift)
  • steipete: Current cache code is attributed to Peter Steinberger, who also merged the initial bounded-scan work. (role: recent cache-area contributor; confidence: high; commits: 53376361a83e, 5bd587850611; files: Sources/CodexBarCore/Vendored/CostUsage/CostUsageCache.swift, Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Have an owner explicitly review the v0.48 upgrade, cross-process contention, and cache-clear recovery boundary on the final head.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (5 earlier review cycles)
  • reviewed 2026-08-07T01:21:26.927Z sha 6dce98a :: found issues before merge. :: [P2] Retain generations until JSON-derived readers release them
  • reviewed 2026-08-07T02:17:10.130Z sha 2556dff :: needs changes before merge. :: [P2] Retain generations until JSON-derived readers release them
  • reviewed 2026-08-07T02:56:19.653Z sha 2556dff :: needs changes before merge. :: [P2] Retain generations until JSON-derived readers finish
  • reviewed 2026-08-07T04:47:34.457Z sha 1946578 :: needs maintainer review before merge. :: none
  • reviewed 2026-08-07T08:03:49.013Z sha 1946578 :: needs maintainer review before merge. :: none

@xx205
xx205 force-pushed the fix/codex-catchup-linear-indexing branch from 6dce98a to 2556dff Compare August 7, 2026 02:10
@clawsweeper clawsweeper Bot added status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Aug 7, 2026
@steipete

steipete commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Decision on this PR — and it's the good kind of decline. Your analysis convinced the owner to go further than the sidecar: the verified review of this PR (quadratic catch-up confirmed at the old head, your linear-work regression proof, invariants preserved) triggered the call to migrate Codex cost storage to a single SQLite database entirely, deleting the JSON artifact and its whole bounding/pruning/producer-key machinery rather than maintaining a dual store. The monolithic decode/encode is the root of every incident this subsystem produced this month (#2637, #2646, #2703), and your append-state design showed the way out.

So this PR won't merge as-is — not because anything in it is wrong, but because "JSON stays publication authority" is the part we're removing. The migration series is starting immediately (tracking issue follows shortly, linking here); your accumulator schema and linearity test design are being carried into it with credit. You're the person who understands this subsystem best from the outside — review on the series would be genuinely valued, and if you'd rather build any phase of it yourself, say so on the tracking issue and it's yours.

Thanks for a rigorous PR — fourth one in this area, and each one has moved the architecture forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants