refactor: derive Codex cost cache producer key from parser source hash#1042
Open
hhh2210 wants to merge 2 commits into
Open
refactor: derive Codex cost cache producer key from parser source hash#1042hhh2210 wants to merge 2 commits into
hhh2210 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds producer-key based invalidation for Codex cost caches while keeping the existing codex-v8.json path stable and leaving non-Codex cache behavior unchanged.
Changes:
- Adds optional
producerKeystorage and matching checks for Codex cache load/save. - Derives Codex producer keys from app/CLI release version, with development executable fingerprint fallback.
- Adds cache behavior and producer-key derivation tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
Sources/CodexBarCore/Vendored/CostUsage/CostUsageCache.swift |
Adds producer-key stamping, validation, and release/development key derivation for Codex caches. |
Tests/CodexBarTests/CostUsageCacheTests.swift |
Adds coverage for producer-key matching, legacy cache invalidation, non-Codex behavior, and version-source selection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The first iteration of this PR stamped the cache with the CodexBar marketing version. That over-fires on cosmetic releases (every release forces a one-time Codex cache rebuild, even when the scanner did not change) and depends on a fallback chain through the executable bundle to discover the version at runtime. Replace it with a content hash of the Codex scanner sources, generated at build time by Scripts/regenerate-codex-parser-hash.sh and emitted as CodexParserHash.value. The producer key is now codex:cu:p<hash>: - Only invalidates when the Codex scanner code actually changes, so cosmetic releases keep the existing cache. - The hash is generated from source, so contributors cannot forget to bump a manual artifactVersion constant. - Scripts/lint.sh re-runs the generator and fails if the tracked file is out of date, so CI catches stale hashes. Non-Codex caches remain on their previous artifact-version behavior.
1f9b0fa to
e7dd706
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
producerKey = "codex:cu:p<hash>", where<hash>is a generated SHA256 prefix over the included non-ClaudeVendored/CostUsage/*.swiftinputscodex-v8.jsonartifact path stable while ignoring legacy or mismatched Codex caches so they rebuild onceCodexParserHashfile plus a non-mutatingScripts/lint.shcheck that fails when the hash is staleWhy
This implements the auto-invalidation design proposed in #1020 along the forgotten-bump axis. The earlier correctness fix in #1014 made oversized
turn_contextrows recover the model from the retained prefix, but a fixed parser can still be hidden by an already-written stalecodex-v8.jsoncache. Continuing to solve that by renaming the file tocodex-v9.json, thencodex-v10.json, is fragile because every future Codex parser semantic fix has to remember to bump the filename.The parser content hash is a conservative build-time producer identity for Codex cost-cache output. It changes when the included non-Claude
Vendored/CostUsage/*.swiftinputs change, so Codex caches rebuild after scanner/cache-source changes without requiring a manualcodex-vN.jsonpath bump. This may over-invalidate on refactors or comments in the included files, but it avoids under-invalidation after parser fixes.The existing artifact version remains the coarse file/schema boundary. This PR removes the need to bump the Codex cache path for ordinary Codex scanner semantic changes; it does not remove the artifact-version mechanism for incompatible cache schema/path changes.
Implementation
Scripts/regenerate-codex-parser-hash.shreads every*.swiftunderSources/CodexBarCore/Vendored/CostUsage/excluding Claude-specific files, concatenates them in a stable order with a separator, takes SHA256, and writes the first 16 hex characters intoSources/CodexBarCore/Generated/CodexParserHash.generated.swift.Scripts/lint.shruns the generator in--checkmode, comparing against a temporary expected file without mutating the worktree.Why this does not close #1020 entirely
#1020 also proposes invalidating on observed
cli_versiondrift (an upstream-shape signal). I prototyped that on top of this PR and pulled back. The expected producer key needs to be known beforeCostUsageCacheIO.loadreturns, but the observedsession_meta.payload.cli_version/originatorset is only knowable after reading session files. Making that real would require either a preflight metadata scan or a separate producer-version index.More importantly, the cache stores scanner output, not raw JSONL bytes. If upstream Codex CLI changes JSONL shape and CodexBar has not shipped a scanner fix, invalidating the cache only reruns the same unchanged scanner and produces the same wrong output. The useful follow-up for that axis is likely telemetry/logging for newly observed producer versions, not cache invalidation itself.
Local benchmark context
From the merged #1017 synthetic shape benchmark on this machine after this patch:
From the real local 30-day Codex sessions tree used in #1016 (~1.24 GB / 545 files / 160k lines), a cold CLI cost refresh took about 14.18s wall-clock, while a cached read took about 1.15s. So this trades a one-time Codex cache rebuild when the included Codex scanner/cache-source inputs actually change for a stable invalidation mechanism that no longer needs manual path-version patches.
Tests
Scripts/regenerate-codex-parser-hash.sh --checkswift test --filter CostUsageCacheTestsswift test --filter CostUsageScripts/lint.sh lintswift testRefs #1020 (forgotten-bump axis only)