Skip to content

fix(codex): one-time rebuild of fork-inflated Codex aggregates (#75)#78

Merged
pitimon merged 1 commit into
mainfrom
fix/75-codex-fork-migration
Jul 22, 2026
Merged

fix(codex): one-time rebuild of fork-inflated Codex aggregates (#75)#78
pitimon merged 1 commit into
mainfrom
fix/75-codex-fork-migration

Conversation

@pitimon

@pitimon pitimon commented Jul 22, 2026

Copy link
Copy Markdown
Owner

What & why

The #75 parser fix (PR #77, merged) corrects only go-forward parsing. Files ingested before it sit at offset = EOF and are never re-read, so their fork-inflated contribution (up to ~40x Cache Read across the local corpus) stays baked into the persisted aggregates — the dashboard keeps showing the old inflated numbers until this migration runs. This is the second half of #75.

The migration

migrateCodexForkOvercountRebuild — run-once (via a cursors.migrations sentinel, key codexForkOvercountRebuild_2026_07), modeled on the adjacent migrateRolloutCumulativeDeltaBuckets, scoped to source=codex/every-code so no other provider is touched. Invoked in cmdSync just before parseRolloutIncremental, so the same run rebuilds corrected numbers.

  1. Drops the per-file cursors → the same-run re-parse reads each file from offset 0 with the fixed accounting (also clears the now-obsolete codexFork cursor field).
  2. Hourly: deletes the inflated accumulator buckets and appends zero rows to queue.jsonl. The dashboard keep-lasts per source|model|hour_start (readQueueData), so the re-parse's corrected row supersedes the zero — or the zero stands for a file no longer on disk.
  3. Project: resets the accumulator buckets without writing zero rows. Files still on disk rebuild and their corrected project row wins via keep-last; a bucket whose file was deleted or whose repo moved keeps its prior queue value rather than being zeroed. (The earlier delta migration already retracted hourly orphans in a prior release, but never touched project — so a zero here would be a brand-new, unrecoverable loss. This conservative path was adopted after review.)

Local-only (no network)

The acceptance test runs cmdSync with a rejecting fetch stub and asserts zero network calls, satisfying #75's local-only requirement.

Review

Two independent reviews (code-reviewer + silent-failure-hunter). code-reviewer: no CRITICAL/HIGH — retraction identity, schema, scoping, and read-path test all verified against primary sources. silent-failure-hunter raised one HIGH (the project zero-retraction was a new data-loss surface for orphaned files); fixed by the conservative accumulator-only reset above, with an orphan-preservation test.

Test plan

test/sync-codex-fork-migration.test.js — 4 tests:

  • direct migration: codex/every-code hourly + project buckets + file cursors reset; other providers (gemini/claude) untouched; hourly zero-retractions carry the right schema; project.queue.jsonl not written
  • orphaned codex project bucket: accumulator reset but its project.queue.jsonl row is byte-identical (no loss)
  • idempotency: the sentinel blocks a second run
  • end-to-end, no network: cmdSync + rejecting fetch stub → 0 network calls; seeded 8.2M-token inflated codex bucket becomes child-only (cached 80 / total 130) on the queue.jsonl read path; a non-codex row survives byte-identical
  • npm run ci:local green (root suite 765/765)

With this merged, #75 is fully resolved (parser fix + historical rebuild) and can be closed.

Refs #75

The parser fix (PR #77) only corrects go-forward parsing. Files ingested
before it sit at offset = EOF and are never re-read, so their fork-inflated
contribution (up to ~40x Cache Read on the local corpus) stays baked into the
persisted aggregates and the dashboard. This adds the one-time rebuild that
retracts that inflated history and forces a clean re-parse.

migrateCodexForkOvercountRebuild (run-once via a migrations sentinel, modeled
on migrateRolloutCumulativeDeltaBuckets, scoped to source=codex/every-code so
no other provider is touched):

- Drops the per-file cursors so the same-run parseRolloutIncremental re-parses
  from offset 0 with the fixed accounting (and clears the obsolete codexFork
  field).
- Retracts the inflated hourly buckets: deletes them from the accumulator and
  appends zero rows to queue.jsonl. The dashboard keep-lasts per
  source|model|hour_start, so the re-parse's corrected row supersedes the zero,
  or the zero stands for a file no longer on disk.
- Resets the project accumulator buckets WITHOUT writing zero rows. Files still
  on disk rebuild and their corrected project row wins via keep-last; a bucket
  whose file was deleted or whose repo moved keeps its prior queue value rather
  than being zeroed. Unlike the hourly path (whose orphans were already
  retracted by the earlier delta migration), project aggregates were never
  retracted before, so a zero here would be a brand-new, unrecoverable loss.

Local-only: no network. The acceptance test runs cmdSync with a rejecting
fetch stub and asserts zero network calls, that the seeded 8.2M-token inflated
codex bucket becomes child-only (cached 80 / total 130) on the queue.jsonl
read path, and that a non-codex row is byte-identical. Additional tests cover
the direct migration (codex reset, other providers untouched), orphaned
project buckets (queue row preserved, no loss), and sentinel idempotency.

Refs #75

Co-authored-by: itarun.p <itarun.p@somapait.com>
@pitimon
pitimon merged commit b5266a9 into main Jul 22, 2026
1 check passed
@pitimon
pitimon deleted the fix/75-codex-fork-migration branch July 22, 2026 00:55
pitimon added a commit that referenced this pull request Jul 22, 2026
Ships the issue #75 Codex subagent fork-history fix that is already on main:
- #77 parser fix (exclude replayed fork parent history from token accounting)
- #78 one-time rebuild migration for already-inflated Codex aggregates

The version bump is what lets npm-publish.yml actually publish main (the job
skips while the package.json version already exists on npm). Required so the
dashboard LaunchAgent and `npx @ipv9/tokentracker-cli sync` pick up the fixed
parser + migration instead of the still-inflated 0.39.38.

Co-authored-by: itarun.p <itarun.p@somapait.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant