Skip to content

feat(codex): a second rail, and the OpenAI cache-write rate it needs - #35

Merged
pa-arth merged 2 commits into
mainfrom
feat/codex-adapter-and-cache-write
Aug 24, 2026
Merged

feat(codex): a second rail, and the OpenAI cache-write rate it needs#35
pa-arth merged 2 commits into
mainfrom
feat/codex-adapter-and-cache-write

Conversation

@pa-arth

@pa-arth pa-arth commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a Codex adapter and fixes an OpenAI cache-write rate. They ship together because they are the same change — the pricing bug was latent only for want of an adapter that reads cache_write_input_tokens.

The adapter (src/adapters/codex.ts, --codex) reads ~/.codex/sessions/**/rollout-*.jsonl into the existing Session/Span model. Verified against the whole local corpus: 25 rollouts, 2499 turns, every token bucket matching an independent walk of the logs exactly. Two traps the format sets:

  • The token conventions are inverted. TurnUsage is additive (Anthropic: input excludes the cache buckets); Codex is subset (input_tokens is the total, cached_input_tokens/cache_write_input_tokens inside it). Reading it straight through double-counts the largest bucket in the file — cache reads are 303M of 313M tokens locally, so this is not a rounding error.
  • token_count rows repeat. Cost comes from last_token_usage (per request), never a difference of the cumulative total_token_usage. An exact repeat of the previous row is suppressed — and repeats are not always adjacent (observed at gaps of 0, 0, 1, 2 and 7 records, one spanning a task boundary), so anything buffered in the gap belongs to the next request. With the suppression all 25 rollouts reconcile to the token; without it, two are over.

--codex is opt-in, and that is a correctness choice rather than caution. Codex encrypts reasoning (1598/1598 records, zero plaintext summaries), has no Read tool and no plan mode, so thinkingChars/reads/modes come back empty — indistinguishable from "measured and found absent". Folded in silently, a Codex-heavy user's fluency signals would drop because of what the log format omits. Session.source now exists so a signal can select its rail. It also writes no history snapshot, never transmits, and is refused alongside --judge/--open: AggregateRecord.tool is a frozen z.literal('claude_code') and cannot describe a two-rail corpus (dropping the Codex half instead would make the shared report differ from the one on screen).

The pricing fix. pricing.ts billed written tokens at o.input under the comment "no separate write bucket" — true until the GPT-5.6 GA (2026-07-09), when OpenAI added a write premium. Every 5.6 cache write was 20% under (sol: cacheWrite 5, input 4), and the same premise understated the compaction counterfactual's write cost, biasing that recommendation toward compacting.

Fixed by re-syncing src/vendor/pricing.ts from @promptster/config-cost (which grew the axis on 2026-08-12), not by hand-editing the mirror — it is verbatim, and sync-pricing.mjs refuses to overwrite local edits. Checked before forcing: all 29 OpenAI and 16 Anthropic keys already agreed on every rate, so the sync is axis-only; it also picks up upstream's longest-key-first Anthropic lookup, which the mirror lacked.

Nothing caught this for six weeks because pricingDrift compares input/cachedInput/output — the fields present on both sides — so a missing rate axis is invisible to it however green it runs. pricingPinned now asserts the axis as a rule (cacheWrite === input * 1.25 on 5.6+, === input below) rather than per-row.

Two things worth knowing before reviewing:

  • The pricing fix is inert on real local data. All 2504 token_count rows report cache_write_input_tokens: 0. The tests use synthetic usage deliberately — a fixture copied from disk would have passed against the broken code too.
  • A pre-existing hole, found but not fixed here: CLAUDE.md claims --root never transmits, but mayTransmit gates only sendCapture. postReport (--open) and judgeFootprints (--judge) are ungated, so --root /fixtures --open uploads a fixture-derived aggregate. That is why --codex refuses the combination rather than trusting mayTransmit. Left alone as a separate product decision about two consent paths.

Also fixes CLAUDE.md saying the package manager is npm — it has been pnpm since #34. The mirror's drift history moved to MAINTAINING.md, since a verbatim mirror deletes any note written into it.

Test plan

  • pnpm typecheck — clean
  • pnpm lint — clean
  • pnpm test460 passed, 44 files (up from 437/42)
  • pnpm build:npm — bundles, and --codex reaches the published artifact's --help
  • New: src/__tests__/codexAdapter.test.ts (17) — subset→additive conversion and its clamp, the double-count stated as arithmetic, non-adjacent duplicate suppression keeping gap content for the next turn, patch paths with the diff body asserted absent from the serialized session, post-compaction span flagging, subagent thread → sidechain + parent link, unpriced codex-auto-review
  • New: src/__tests__/openaiCacheWrite.test.ts (6) — 5.6 writes at 1.25x with the pre-fix arithmetic named, pre-5.6 unchanged and not zero, both write slots against the one rate, three input buckets at three distinct rates, Anthropic arm untouched
  • Adapter cross-checked against an independent Python walk of ~/.codex: 2499 turns, uncached 9,667,619 / cacheRead 303,627,392 / write 0 / output 486,886 — exact match on every bucket, including the 5 suppressed duplicates
  • End-to-end node dist/cli.js --codex --json — stdout stays pure JSON, notice on stderr; --codex --open exits 2 with the reason

🤖 Generated with Claude Code

Adds `adapters/codex.ts`, reading ~/.codex/sessions rollouts into the same
Session/Span model. The two changes ship together because they are the same
change: the OpenAI cache-write bug below was latent only for want of an
adapter that reads `cache_write_input_tokens`.

- The token conventions are INVERTED between the rails. TurnUsage is additive
  (Anthropic: `input` excludes the cache buckets); Codex is subset
  (`input_tokens` is the total, cached/write sit inside it). Reading it straight
  through double-counts the largest bucket in the file — cache reads are 303M of
  313M tokens across the local corpus. Verified: 25 rollouts, 2499 turns, every
  bucket matching an independent walk of the logs exactly.

- `token_count` rows repeat. Cost comes from `last_token_usage` (per request),
  never a difference of the cumulative, with an exact repeat of the previous row
  suppressed. Repeats are NOT always adjacent — observed at gaps of 0, 0, 1, 2
  and 7 records, one spanning a task boundary — so anything buffered in the gap
  belongs to the NEXT request. With the suppression all 25 rollouts reconcile to
  the token; without it, two are over.

- Opt-in behind --codex, which is a correctness choice. Codex encrypts reasoning
  (1598/1598 records, zero plaintext summaries), has no Read tool and no plan
  mode, so thinkingChars/reads/modes come back empty — indistinguishable from
  "measured and found absent". Folded in silently, a Codex-heavy user's fluency
  signals would fall because of what the log format omits. Session.source now
  exists so a signal can select its rail.

- --codex writes no history snapshot, never transmits, and is REFUSED alongside
  --judge/--open: AggregateRecord.tool is a frozen z.literal('claude_code') and
  cannot describe a two-rail corpus. Dropping the Codex half instead would make
  the shared report differ from the one on screen.

fix(pricing): OpenAI cache writes were billed at the plain input rate. The
comment read "no separate write bucket" — true until the GPT-5.6 GA
(2026-07-09), when OpenAI added a write premium, so every 5.6 write was 20%
under (sol: cacheWrite 5, input 4). The same premise understated the compaction
counterfactual's write cost, biasing that recommendation toward compacting.

Fixed by re-syncing src/vendor/pricing.ts from @promptster/config-cost, which
grew the cacheWrite axis on 2026-08-12 — not by hand-editing the mirror, which
is verbatim and whose sync script refuses to overwrite local edits. Rates are
otherwise unchanged (all 29 OpenAI and 16 Anthropic keys already agreed); the
re-sync also picks up upstream's longest-key-first Anthropic lookup.

Nothing caught this for six weeks because pricingDrift compares
input/cachedInput/output — the fields present on both sides — so a missing rate
AXIS is invisible to it however green it runs. pricingPinned now asserts the
axis as a rule rather than per-row. The mirror's drift history moved to
MAINTAINING.md, since a verbatim mirror deletes any note written into it.

Note the fix is inert on the local corpus: all 2504 token_count rows report
cache_write_input_tokens: 0, so the tests use synthetic usage on purpose — a
fixture copied from disk would have passed against the broken code too.

Also: CLAUDE.md said the package manager was npm; it has been pnpm since #34.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

Adds opt-in Codex rollout ingestion and extends OpenAI pricing with explicit cache-write rates.

  • Normalizes Codex sessions, turns, token buckets, tool activity, edits, compaction boundaries, and subagent relationships.
  • Adds --codex orchestration while disabling history and remote operations for mixed-rail runs.
  • Re-syncs vendored pricing, applies OpenAI cache-write tariffs, and adds regression coverage.
  • Introduces Session.source to distinguish telemetry rails, although source-sensitive fluency analysis does not yet use it.

Confidence Score: 4/5

The source-sensitive fluency calculations need to be fixed before merging because --codex currently reports unobservable plan-mode data as measured zero.

Codex sessions are intentionally marked with a source discriminator and empty unobservable fields, but the shared fluency path ignores that discriminator, lowering plan-mode metrics and potentially the user’s displayed self-band.

Files Needing Attention: src/cli.ts, src/model.ts, src/adapters/codex.ts

Important Files Changed

Filename Overview
src/adapters/codex.ts Adds comprehensive Codex rollout parsing, usage normalization, duplicate suppression, and source-specific session metadata.
src/cli.ts Adds opt-in Codex loading and local-only guards, but passes unobservable Codex plan-mode values into common fluency analysis.
src/model.ts Adds an optional source discriminator intended to support rail-sensitive analysis.
src/pricing.ts Correctly switches OpenAI cache-write billing and compaction estimates to the new per-model write tariff.
src/vendor/pricing.ts Re-syncs vendor pricing with cache-write rates and more constrained, longest-key-first Anthropic model matching.
src/tests/codexAdapter.test.ts Covers Codex parsing, token normalization, duplicate rows, privacy boundaries, compaction, and subagent modeling.
src/tests/openaiCacheWrite.test.ts Exercises premium and legacy OpenAI cache-write rates and protects Anthropic’s separate write buckets.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Claude[Claude Code transcripts] --> ClaudeAdapter[Claude adapter]
  Codex[Codex rollouts under --codex] --> CodexAdapter[Codex adapter]
  ClaudeAdapter --> Sessions[Normalized Session array]
  CodexAdapter --> Sessions
  Sessions --> Audit[runAudit]
  Audit --> Pricing[Spend and token pricing]
  Audit --> Fluency[Fluency analysis]
  Pricing --> Report[Local report / JSON]
  Fluency --> Report
  CodexAdapter -. source: codex .-> Fluency
Loading

Fix all with Greploop Fix All in Claude Code Fix All in Cursor

Reviews (1): Last reviewed commit: "feat(codex): a second rail, and the Open..." | Re-trigger Greptile

Comment thread src/cli.ts
'plan mode are NOT observable on that rail and are absent rather than zero.\n',
);
}
sessions.push(...codex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unobservable plan mode lowers fluency

When --codex adds substantive Codex sessions, the shared fluency analysis counts their unobservable modes: [] values in the plan-mode denominator, causing the reported plan-mode rate and potentially the self-band to be understated; filter source-sensitive signals using Session.source or represent them as unknown.

Knowledge Base Used: Usage economics and pricing

Fix in Claude Code Fix in Cursor

Codex rollouts always report modes: [] — plan mode is unobservable on
that rail, not absent. Counting those sessions in the substantive-session
denominator (without a plan-mode credit) understated planModeRate for any
--codex run, exactly the "absent, not zero" pitfall Session.source exists
to guard against. Exclude source === 'codex' sessions from both sides of
the ratio instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pa-arth
pa-arth merged commit d81ab8b into main Aug 24, 2026
2 checks passed
@pa-arth
pa-arth deleted the feat/codex-adapter-and-cache-write branch August 24, 2026 21:12
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