Repository navigation
v1.7.0: Codex analytics - #54
Merged
Merged
Conversation
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>
… 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>
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.
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.
stats,today,forecast,trend,impact,why,receiptanddriftread Codex logs (~/.codex/sessions,~/.codex/archived_sessions, orCODEX_HOME).--agent codexshows Codex only;--herematches the working folder Codex recorded.--agentflag and both tools installed: one section per tool onstats,today,forecast,trend,impact; aBoth tools:line onstats,today,forecast; a hint naming the other tool's latest session onwhy,receipt,drift.--jsonstays one tool per call and notes Codex on stderr.todayabout 30-43s to about 1.4s andforecast24-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).driftabout 6.1s to 2.3s cold and 1.24s to 0.29s warm; its cache moved to~/.sipcode/drift/sessions-v3.jsonl(rebuilt automatically).--agent,--here,--session <id>.agentinput; still 15 tools.verify_sipcode_impactoutput now parses as JSON.stats --since allno longer says "20735 days".*.snappinned to LF in.gitattributesso Windows test runs leave the tree clean.statson the maintainer's logs is about 1.5 GB (about 3.2 GB on 1.6.21).~/.claude/projectsno longer hides Codex results;--herekeeps cross-folder dedupe; drift uses one empty-session rule;--jsonand MCP note unpriced tokens; Codex rows added for every model OpenAI lists a price for; the GitHub Action now runssipcode(it ran@sipcode/cli);todayre-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.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.stats,today,forecast,trend,impactprint a Claude Code and a Codex section (exit 0).statsandforecastend 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).todayprinted no combined line because Codex had no session today (by design: the line needs two sections with totals).why,drift,receiptreport the newest session and printCodex also has a recent session (<id>): run with --agent codex.CODEX_HOMEset to a missing folder):stats,today,forecastprint one section, no header, no combined line.SIPCODE_PROJECTS_DIRset to a missing folder): all eight commands show Codex only, exit 0.stats --json --agent codexparses;stats --jsonparses and printsnote: Codex logs found too.on stderr.Not supported for Codex yet
AGENTS.md(rules, and the rules step ofinit).proxy,hygiene).estimateandbenchmark..jsonl.zst) are skipped with a count.Publishing (tag
v1.7.0, npm, MCP registryserver.json, site deploy) waits for the maintainer's approval.🤖 Generated with Claude Code