Skip to content

v1.7.0: Codex analytics - #54

Merged
Anuj7411 merged 100 commits into
mainfrom
codex-support
Oct 10, 2026
Merged

Anuj7411 merged 100 commits into
mainfrom
codex-support

Conversation

@Anuj7411

Copy link
Copy Markdown
Owner

v1.7.0: Codex analytics

Summary

Sipcode now reads OpenAI Codex CLI session logs for spend analytics, and Claude Code totals no longer count requests that a resumed session copies from its parent.

  • Codex analytics. stats, today, forecast, trend, impact, why, receipt and drift read Codex logs (~/.codex/sessions, ~/.codex/archived_sessions, or CODEX_HOME). --agent codex shows Codex only; --here matches the working folder Codex recorded.
  • Both tools in one view. With no --agent flag and both tools installed: one section per tool on stats, today, forecast, trend, impact; a Both tools: line on stats, today, forecast; a hint naming the other tool's latest session on why, receipt, drift. --json stays one tool per call and notes Codex on stderr.
  • Claude Code cross-file dedupe. On one real machine, counting exactly the files Sipcode reads: 13.37B tokens summed vs 12.00B unique, 11.4% over (3,163 repeated requests in 8 resumed-session files). Each request now counts once.
  • why / receipt on a resumed session no longer include the parent's copied requests (worst real case: shown as $218.00, own spend $10.20).
  • OpenAI pricing from developers.openai.com/api/docs/pricing and the Codex model pages (as of 2026-10-09), long-context tier over 272,000 input tokens. Unknown models are shown as unpriced, never $0.
  • Speed. today about 30-43s to about 1.4s and forecast 24-32s to about 0.5s on a warm cache (byte-identical output; first run unchanged), via a local cache at ~/.sipcode/usage-cache/<agent>.json (no message text, never uploaded). drift about 6.1s to 2.3s cold and 1.24s to 0.29s warm; its cache moved to ~/.sipcode/drift/sessions-v3.jsonl (rebuilt automatically).
  • drift gains --agent, --here, --session <id>.
  • MCP: 7 session tools take an optional agent input; still 15 tools. verify_sipcode_impact output now parses as JSON.
  • stats --since all no longer says "20735 days".
  • Copy: commands without Codex support say "not supported for Codex yet" (E009) with no release promise.
  • Tests: three slow tests got measured per-test timeouts; *.snap pinned to LF in .gitattributes so Windows test runs leave the tree clean.
  • Memory. Sessions stream through a dedupe index instead of all being held at once: peak memory for a full-history stats on the maintainer's logs is about 1.5 GB (about 3.2 GB on 1.6.21).
  • Fixes from the final review: an empty ~/.claude/projects no longer hides Codex results; --here keeps cross-folder dedupe; drift uses one empty-session rule; --json and MCP note unpriced tokens; Codex rows added for every model OpenAI lists a price for; the GitHub Action now runs sipcode (it ran @sipcode/cli); today re-read count off by one; impact labels Codex before/after as not caused by Sipcode.

Verification

  • npm run lint, npm run build: green.
  • npx vitest run: 1,996 passed in 193 files (unit, integration and e2e). npm run test:e2e: 19 passed in 3 files.
  • Full suite run 3 more times after the timeout change: 0 timeouts, 0 failures.
  • node scripts/verify-counts.mjs (independent counter, imports nothing from Sipcode, every period command cold and warm cache) on a frozen copy of the maintainer's real logs, final run with the code at e23d5c2 (later commits change docs and wording only): 210 MATCH, 0 MISMATCH, exit 0. Claude Code: 188 files, 26,782 requests, 3,182 copied requests dropped across 8 files. Codex: 15 files, 930 requests, 87,842,772 tokens, $55.95, 180,503 unpriced tokens.
  • Earlier run (code at e6bd1a2), full figures:
    • Claude Code: 181 files, 175 non-empty sessions, 26,747 requests, 12,447,083,353 tokens, $10,030.79, 3,182 copied requests dropped across 8 files, 0 unpriced tokens.
    • Codex: 15 files, 15 non-empty sessions, 930 requests, 87,842,772 tokens, $55.95, 0 copied requests, 180,503 unpriced tokens (one subagent thread, counted in every period total).
    • why samples: Claude Code 14 sessions (8 hold copied requests), Codex 6; 0 sampled sessions whose own-file values differ from the max across copies.
  • Real-data runs on the maintainer's machine (structure checked, numbers only):
    • Both tools installed: stats, today, forecast, trend, impact print a Claude Code and a Codex section (exit 0). stats and forecast end with the combined line (Both tools: 5.6B tokens · ~$3,513 + ~$1 (+ unpriced) for the 30-day window; Both tools: ~$1,848 + ~$3 (+ unpriced) for the forecast). today printed no combined line because Codex had no session today (by design: the line needs two sections with totals). why, drift, receipt report the newest session and print Codex also has a recent session (<id>): run with --agent codex.
    • Claude Code only (CODEX_HOME set to a missing folder): stats, today, forecast print one section, no header, no combined line.
    • Codex only (SIPCODE_PROJECTS_DIR set to a missing folder): all eight commands show Codex only, exit 0.
    • stats --json --agent codex parses; stats --json parses and prints note: Codex logs found too. on stderr.

Not supported for Codex yet

  • Writing rules to AGENTS.md (rules, and the rules step of init).
  • Registering the MCP server in Codex.
  • The proxy and hooks (proxy, hygiene).
  • estimate and benchmark.
  • Compressed Codex logs (.jsonl.zst) are skipped with a count.

Publishing (tag v1.7.0, npm, MCP registry server.json, site deploy) waits for the maintainer's approval.

🤖 Generated with Claude Code

Anuj7411 and others added 30 commits October 8, 2026 12:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s resume

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x key

Review findings on Task 2: filtering by mtime before cross-file dedupe makes
--since 7d disagree with --since all; a root+total legacy key can collide
between a parent and a subagent counter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ude the copied read

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verified equivalent to parseTranscript (keys, keyless count, start/end) on
fixtures, synthetic edge cases and 1497 real transcripts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r dedupe only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fallback for nested look-alikes

- Keys and first/last timestamps were slices of the file text, so every
  KeyScan retained its whole file until dedupe finished (136 files / 2.2 GB:
  4,390 MB retained; a fully out-of-window load ran out of heap). Strings are
  now copied (Buffer round-trip). Retained heap after scanning is 6 MB
  (parse: 74 MB). Regression test scanKeysMemory.test.ts fails without the copy.
- Lines where a type/timestamp/message/requestId marker repeats, a line with
  both type markers, or a message without a content field / with its id after
  the content fall back to JSON.parse of that single line. Synthetic tests
  cover tool_use input timestamp, toolUseResult nesting an assistant object,
  progress lines, no content field and id after content.
- KeyScan now lives in transcript/parse.ts and is re-exported from
  agents/types.ts (transcript no longer imports from agents).
- Real data: 1513/1513 files match parseTranscript on keys, keyless count,
  startedAt, endedAt. Scan ~3.5x faster than parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s files once

Period commands now go through loadSessions (discover, --here, parse,
cross-file dedupe) instead of five copy-pasted loops, so a resumed Claude
session no longer counts requests it copied from its original (11.4%
over-count on real data). stats and trend pass windowSinceMs so older
files are only key-scanned. today, forecast and impact load full history
because their reports depend on the earliest session. Unreadable files
and parse issues are now surfaced instead of silently skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fast path now hands a line to JSON.parse when an assistant line lacks
a timestamp or requestId marker, when a "timestamp" key is not followed
by a string, or when a user line holds more than one message object. The
memory test is skipped, not failed, when gc cannot be obtained.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es unreadable files

A resumed file can re-log copied requests with top-level usage zeroed.
dedupeAcrossSessions now gives the kept copy the largest value per usage
field across all copies (tool calls mirror the turn), so the result no
longer depends on which copy wins the ordering. Sessions with nothing to
change are still returned as the same object. impact prints the same
unreadable/parse-issue note as the other period commands, and a test pins
that a resumed session crossing the install pivot counts its copied
history once in "before".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session whose kept turns only had usage raised (nothing dropped) went
through rebuild, which recomputed startedAt/endedAt/durationSec from
assistant timestamps alone and so lost the user-line timestamps the parser
includes in the span. The span is now recomputed only when turns were
dropped. Also documents that the kept copy carries the largest usage across
all copies and why raising the 1h cache part independently is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gpt-5.1-codex-mini no longer borrows gpt-5.1's price; unknown models stay unpriced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s price

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…numbered-read idioms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turns a Codex rollout into the shared ParsedSession.
- token_usage_record (Codex >= 0.153) gives one turn per response_id.
- Older files use token_count. A repeated total is skipped. When
  last_token_usage does not account for the change, the parser bills
  the change in the cumulative total. Real logs carry zero-usage
  estimates after hidden requests.
- cached and cache-write are counted as parts of input, so
  inputTokens = input - cached - write.
- Shell reads become Read calls with absolute paths, resolved against
  workdir, then the turn cwd, then the session cwd. Reads that failed
  (exit code, abort, Script failed, PowerShell or coreutils
  not-found) stay Bash.
- Commands inside the JavaScript exec wrapper are extracted.
- apply_patch becomes one Edit per file.

Validated on 15 real rollouts: every per-file sum equals Codex's own
final total.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rror lines to each tool's layout

exec_command (unified exec) logs 'Chunk ID/Wall time/Process exited with code N/Output:'.
That header was never checked, so a failed read via exec_command became a Read and its
retry a false duplicate. The first-line error match now requires the tool's own layout
(PowerShell 'Get-Content :', coreutils 'cat: <path>: <msg>'), so file text such as
'type: ... does not exist' on line 1 stays a read. Real-log totals unchanged (5 failed reads).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of stopping

A 'tools.exec_command({' inside a comment or an odd template literal made the brace
scan run off the end and abandon every later command in the script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed the process drive)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lists rollout-*.jsonl under $CODEX_HOME/sessions and archived_sessions
(sessions/ wins for the same file name). Reads only line 1 via the new
FileSystem.readHead (1 MiB, UTF-8 safe at the cut; full read only when
line 1 is longer). Skipped .jsonl.zst files are listed and unreadable
folders/files counted, so callers can report them. A missing folder is
not an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Registers "codex" (AgentId, ALL_AGENT_IDS, registry). The adapter
discovers rollouts, parses them, and reads AGENTS.md; writing rules
returns E009 until Codex setup lands.

- isInstalled: $CODEX_HOME/sessions holds at least one rollout-*.jsonl.
- --here: session cwd equals or is inside the folder, compared in the
  path flavour it was written in (win32: case-insensitive, either
  slash; POSIX: case-sensitive), at whole path segments, never
  resolved against process.cwd().
- Discovery problems reach the user: discoverSessions may return
  { sessions, unreadable, issues }; loadSessions adds them to its
  unreadable count and issues (one E009 per skipped .jsonl.zst).
  Claude Code and Cursor adapters are unchanged.
- --agent auto still resolves to Claude Code when Codex is installed.
- why / receipt / estimate refuse --agent codex with E009 instead of
  silently showing Claude Code data under a Codex flag.
- rules --agent codex --uninstall no longer runs the Cursor helper on
  AGENTS.md (it could have rewritten the file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
resolveDisplayAgents keeps the Claude Code / Cursor auto-detection and adds
a Codex section when Codex is installed (Codex alone when Claude Code has no
transcripts). JSON stays one agent, with a stderr note naming --agent codex.
combinedLine reuses the shared token/number formatters. discoverAgentSessions
is the one normalised entry point to an agent's discovery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anuj7411 and others added 29 commits October 9, 2026 16:38
… them all

Period commands held every parsed session at once for cross-file dedupe
(synthetic 480k-request fixture: today cold 752 MB, stats --since all
642 MB peak RSS). loadSessions now reads, parses, indexes and analyzes one
file at a time and keeps only the caller's per-session result:

- DedupeIndex keeps per request key only its owner and largest usage
  (keys packed in a byte table); dedupeAcrossSessions is built on it and
  is checked against the previous implementation on random cases.
- Files are read oldest first and each session's copy candidates are added
  before it is analyzed; a session a later file still changes is read
  again at the end (2 of 188 files on one real machine).
- Windowed stats/trend read only out-of-window files that can hold a copy
  (same project, written after the session started; Codex: its family),
  not every older log.
- The usage cache is JSON Lines, read and written line by line.

Synthetic fixture: stats --since all 642 -> 238 MB (main 210), today cold
752 -> 265 MB (main 187), stats 30d 14.3 -> 7.0 s (main 6.5-7.1 s).
Outputs are unchanged on real logs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With --here, sessions were filtered before dedupe, so a Codex fork whose
parent ran in another folder kept the parent's copied requests: period
commands counted them while why (which dedupes against every log) did not.
Logs outside the scope now take part in dedupe as claimers (read only when
they can hold a copy: the fork's family for Codex, the same project folder
for Claude Code), and only in-scope sessions are reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto-detect treated an existing but empty ~/.claude/projects as "Claude
Code has logs": why --json, receipt --json and MCP audit_latest_session
then failed with E003 while Codex had sessions, and the Codex hint was
dropped. In a folder with .cursor/, period commands never looked at Claude
Code logs. One rule now decides for every command (hasClaudeTranscripts:
a .jsonl in a non-observer project folder); a Cursor pick shows the tools
that have logs, and keeps its E009 only when none do. --agent is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
drift's warm pick (from its cache) treated any session with assistant
turns as usable, while the cold pick (pickFrom) skips sessions with no
token usage, so a session with turns but zero usage became "latest" only
when its metrics were cached. Both, and the baseline, now use
hasTokenUsage, the rule isEmptySession is built on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tokens on a model with no known price reached JSON and MCP consumers as $0
with no signal (only why/receipt printed the note). stats, today, forecast,
trend and impact --json now print the same note on stderr; MCP tools return
it as its own text item after the JSON. JSON schemas are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OpenAI table had only gpt-5.3-codex, so sessions on gpt-5-codex,
gpt-5.1-codex(-max, -mini), gpt-5.2-codex and codex-mini-latest showed as
unpriced. Rows added from each model's page on developers.openai.com
(checked 2026-10-09, together with every row of the pricing table, which
is unchanged); the table is now dated 2026-10-09. Models with no official
price (codex-auto-review) stay unpriced; no aliases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mmand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no install marker, impact told Codex users to run `sipcode rules
--install`, which returns E009 for Codex. Codex now gets the wording used
elsewhere (setting Sipcode up inside Codex is not supported for Codex
yet) and the --since option. Claude Code output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sipcode does not act inside Codex, so a Codex before/after difference is
not its doing. Wherever one is shown, one line says so: under the pivot in
the terminal report, on stderr with --json (schema unchanged), and as its
own text item after the JSON in MCP verify_sipcode_impact. The comparison
itself is unchanged; Claude Code output is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stderr

Notes about logs left out of the totals (compressed, unreadable) were
dropped in JSON mode; they now go to stderr as in terminal mode, and the
JSON on stdout is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-reads)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s archived_sessions too)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s.html for Claude Code

A Codex-only or --agent codex run used to write stats.html; the name now
follows the tool, not the section order. Documented in the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…OpenAI table date

Costs in a Codex section come from the OpenAI table, but stats showed the
Anthropic table's date (JSON metaPricing.asOf, terminal footer). Schema
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tSession, compressedFiles, unused messages)

findSessionById and pickLatestSession were only used by tests, which now go
through the real path (listAgentSessions + pickFrom). listSessionsHere,
CodexDiscovery.compressedFiles, MESSAGES.notImplemented and
MESSAGES.hygieneMcpDeferred had no callers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… log folders, fs seam)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r left

A process killed between writing the cache's temp file and renaming it
left <cache>.<pid>.<n>.tmp behind for good. Reading the cache now removes
such files once untouched for 15 minutes (a live writer writes every
1 MB), never a recent one another process may be writing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GitHub Action and its README ran `npx @sipcode/cli@latest`; the
package is `sipcode`. The README's `uses:` now names the real repo
(Anuj7411/sipcode). A guard test checks the package name and that every
flag the action passes exists on the score command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… nvm), not apt

Ubuntu's and Debian's own nodejs package is often older than Node 20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps the substance: any future cloud feature would be opt-in, off by
default and announced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o version promise

The messages and --help text promised them for "v1.1+". They now say
plainly that the flags are not supported and what to do instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0 mismatch)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… kept alive

fs.promises.readFile(p, "utf-8") kept the previous large file in memory
while the next one was read. On one real log folder (largest log 467 MB),
stats --since all needed 1,625 MB of heap when logs were read oldest first,
against 912 MB for 1.6 (newest first). Reading a Buffer and decoding it
gives the same text and 925 MB in either order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…4 GB in 1.6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npx vitest run already includes tests/e2e (193 files, 1,996 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Anuj7411
Anuj7411 merged commit 0543541 into main Oct 10, 2026
3 checks passed
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