Skip to content

Add opt-in OpenCode local history for the ambient Codex ledger - #4574

Closed
Danielweng0902 wants to merge 9 commits into
steipete:mainfrom
Danielweng0902:feat/opencode-local-history
Closed

Danielweng0902 wants to merge 9 commits into
steipete:mainfrom
Danielweng0902:feat/opencode-local-history

Conversation

@Danielweng0902

@Danielweng0902 Danielweng0902 commented Oct 16, 2026 •

Copy link
Copy Markdown

Add opt-in OpenCode local history for the ambient Codex ledger

Closes #3273 (reopened per maintainer invitation: "A PR adding an OpenCode SQLite reader … would be welcome").

What this adds

An opt-in, default-off Codex setting — "Include OpenCode local history (OpenAI)" — that merges machine-local OpenCode usage for assistant records whose provider is exactly openai into this Mac's ambient Codex menu/CLI history, so Codex usage generated through OpenCode finally appears in CodexBar's Codex totals (the original reporter's workflow).

It follows the bounded contract worked out in #3273:

  • Usage metadata only, read-only. SQLITE_OPEN_READONLY with the same WAL/immutable fallback the OpenCode Go reader uses. Prompts, responses, tool output, and auth.json are never read; rows are never attributed to an account or workspace.
  • Exact provider selection. Only providerID == 'openai' assistant records (v1 message $.providerID; v2 session_message $.model.providerID). Other providers in the same database are never selected.
  • Step-finish precedence. Valid step-finish parts win; message-level totals contribute only when a message has no valid parts — mirroring the established OpenCode Go reader behavior.
  • Stable-ID deduplication only. OpenCode v2 copies legacy message rows into session_message under the same IDs (on my database: 4,064 v1 / 4,552 v2 rows, 3,927 shared IDs). v2 rows only contribute IDs the legacy table does not already hold (NOT EXISTS). No fuzzy timestamp/token matching across sources.
  • Estimate semantics. Recorded token dimensions are preserved (input/output/reasoning/cache read+write). Recorded cost is surfaced as-is: OpenCode records $0 for subscription usage, and that $0 is never reinterpreted as spend or a billing receipt. No pricing is computed for these rows.
  • Visible provenance. Merged model names carry an (OpenCode local) suffix (e.g. gpt-5.1-codex (OpenCode local)), entries carry a recorded-cost marker, and snapshots report sourceRecorded/mixed provenance instead of a pure list-price claim — CLI titles, hints, and JSON follow, so recorded costs never hide under an estimate label.
  • Grouped accounting. OpenCode rows join the unattributed project bucket in project-grouped output (the established Pi treatment) in both the foreground scan and the cached catch-up publication. Session-grouped text stays native-only with an explicit warning, exactly like Pi; JSON keeps the merged rows.
  • Permanently unscoped. The source joins only the ambient Codex ledger. Scoped CODEX_HOME scans (whether selected through the codexHomePath argument or the CODEX_HOME environment variable), managed accounts, profile homes, and the Spend Dashboard always exclude it. Historical rows can never be assigned to a Codex account from today's credentials.
  • Upstream database contract. OPENCODE_DB selects exactly one database — :memory: reads as no on-disk history, never a substitute — and without an override the release database under the XDG data root is the selection (absolute XDG_DATA_HOME honored, relative ignored per the XDG spec), matching upstream database.path()'s single-path contract. OPENCODE_UPDATE_CHANNEL is not consulted; upstream derives the channel from a build constant.

Implementation

  • OpenCodeLocalUsageReader (Sources/CodexBarCore/Providers/OpenCode/): self-contained reader modeled on OpenCodeGoLocalUsageReader. Resolves the data root the way OpenCode's xdg-basedir dependency does, then the single database per upstream's selection contract, reads both table generations, and emits a merged CostUsageDailyReport whose entries are marked costIsSourceRecorded. Day buckets are keyed with the supplied reporting calendar, and the window's lower bound is normalized to that calendar's start-of-day so earlier-today turns survive the now-anchored foreground window. A missing database or one with no matching rows reads as nil, not an error; SQLite handles are closed even when sqlite3_open_v2 fails.
  • Merge sites (CostUsageFetcher): the Pi-precedent ambient merge, in both the foreground local scan and the cached catch-up publication. Both sites gate on codexHistoryIsAmbient (no codexHomePath argument, no CODEX_HOME in the environment the native scanner honors), append the unattributed project breakdown for grouped accounting, clear session breakdowns, and compute the snapshot's cost provenance (mixed when estimated rows coexist, sourceRecorded otherwise).
  • Cost provenance (CostProvenance): a new sourceRecorded case with window narrowing that keeps mixed windows mixed. CostUsageDailyReport.Entry gains a costIsSourceRecorded marker that persists through caches (older caches decode it as false) and ORs across same-day merges.
  • CLI and menu: Codex cost titles, default-view footers, and the menu usage hints all follow the snapshot's provenance, so recorded rows are never described as computed list-price estimates; --group-by session runs a native-only load with an explicit omission warning (the Pi session-view precedent), while --group-by project and JSON keep the merged rows.
  • Gating: shouldIncludeOpenCodeLocalHistoryInTokenSnapshot (Codex + ambient home + setting), threaded through refresh, hydration, catch-up, codexbar cost, and the serve cost endpoint. The Spend Dashboard path passes false explicitly. The setting participates in the token snapshot scope signature and menu-refresh readiness key, so toggling it triggers a refresh.
  • Settings: codexIncludeOpenCodeLocalHistory (UserDefaults, default false) with a Codex settings toggle whose subtitle states the privacy and estimate contract.

Tests

  • OpenCodeLocalUsageReaderTests (17 tests): exact openai selection among mixed providers, step-finish precedence without double counting, v2 session messages adding new turns without double counting migrated IDs, v2-only databases, recorded-zero-cost token history, malformed/legacy row skipping, negative/overflowing token counts, window filtering, clock-time lower bound normalizing to the reporting day's start (pinned UTC calendar), day keys following the supplied reporting calendar, XDG_DATA_HOME relocation (absolute honored, relative ignored), the upstream single-database selection contract (:memory: unavailable, no substitution after an explicit selection, no channel fallbacks), missing/empty database → nil, idle WAL without sidecars, active WAL frames, and OPENCODE_DB path resolution.
  • OpenCodeLocalHistoryMergeTests (10 tests): foreground ambient merge on/off with visible provenance labels, mixed-history grouped accounting (native project + unattributed bucket + empty sessions + mixed window provenance), scoped codexHomePath exclusion, CODEX_HOME-environment exclusion (foreground and cached publication), Claude scans never merging, cached publication merge on/off with provenance parity, and legacy-entry decoding (pre-feature caches keep loading, costIsSourceRecorded decodes as false) plus a recorded-entry encode/decode round trip.

Verified against a real OpenCode v2.0.29 database (both table generations present with real migration duplicates): the composed SQL selects exactly the expected rows and deduplicates shared IDs correctly.

Real-behavior verification (CLI)

Built CodexBarCLI from this branch and ran it against a synthetic OpenCode database (two openai turns: 1,545 tokens today 10:00, 800 tokens yesterday 08:00, both recorded $0, plus a claude-opus row that must never appear) on a machine with real native Codex usage:

Scenario Command environment Result
Setting off (default) OPENCODE_DB=<synthetic> No OpenCode rows in any view
Setting on, ambient OPENCODE_DB=<synthetic> Both days gain gpt-5.1-codex (OpenCode local) breakdown rows; --group-by project shows the unattributed bucket (2,345 tokens); JSON reports provenance: mixed; title reads "API-equivalent estimate + source-recorded cost"
Setting on, scoped home OPENCODE_DB=<synthetic> CODEX_HOME=<empty dir> Zero rows: empty native home and the OpenCode merge excluded
Setting on, session view OPENCODE_DB=<synthetic> --group-by session Native conversations only, with the explicit omission warning on stderr (same contract as Pi)
Setting on, relocated data XDG_DATA_HOME=<dir holding the db> Rows still discovered (no OPENCODE_DB); a relative XDG_DATA_HOME is ignored
Setting persistence defaults write/delete + an unrelated key Toggle on/off flips the merge; the unrelated preference survives both directions
Latest-stable upgrade branch CLI against the machine's real settings, ~/.codexbar caches, and native history Existing preferences (59 keys) and history totals unchanged; enabling adds exactly one key and the provenance-aware title/hint appear; disabling removes it

The earlier-today turn (10:00) appears under a --days 1 window anchored at now because the reader normalizes the bound to the reporting day's start; the yesterday-morning turn lands on its own calendar day under --days 2. Legacy-entry decoding and cached-publication parity are covered by tests. Full terminal transcripts are in the review-response comments.

Notes / open questions for review

  • The reader intentionally does not read auth.json (the Go reader uses it for not-detected detection). Presence of the database itself is the availability signal.
  • Overlaps with fix(opencodego): read OpenCode v2 session history for local cost #4526's session_message fix for the OpenCode Go reader are SQL-shape only; that PR fixes the Go provider's reader, this one adds a new ambient Codex source. Happy to rebase if that lands first.

Checklist

  • Reader + gating unit tests added and passing
  • ProviderArchitectureGatekeeperTests passing (new clusters carry // Provider-specific by design: reasons)
  • Docs updated (docs/providers.md, docs/codex.md, docs/opencode.md, CHANGELOG.md)
  • Real-behavior CLI verification (off/on, scoped exclusion, session-view exclusion, grouped accounting, XDG relocation, setting persistence) — transcripts in comments

Add a default-off Codex source that merges machine-local OpenCode usage
for openai-provider assistant records into the ambient Codex menu/CLI
history (steipete#3273):

- OpenCodeLocalUsageReader reads usage metadata only, read-only, from
  the database OpenCode itself resolves (OPENCODE_DB, channel db,
  default), across the v1 message/part tables with step-finish
  precedence and v2 session_message rows deduplicated by stable message
  ID. Recorded cost is surfaced as-is; $0 subscription cost is never
  reinterpreted as spend. auth.json and message content are never read.
- The source is permanently unscoped: it merges only into the ambient
  Codex ledger (foreground scan and cached catch-up publication) and is
  excluded from scoped CODEX_HOME scans, managed accounts, and the
  Spend Dashboard, mirroring the ambient Pi merge precedent.
- Settings toggle 'Include OpenCode local history (OpenAI)' gates the
  merge and participates in the snapshot scope signature; codexbar cost
  and the serve cost endpoint honor the same setting.

Reader and gating tests cover mixed-provider selection, step-finish
precedence, v1/v2 dedup, idle/active WAL reads, path resolution,
scoped-home exclusion, and cached-publication gating.
@clawsweeper

clawsweeper Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:feature Preliminary submission classification; full source review pending. P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed clawsweeper:intake:feature Preliminary submission classification; full source review pending. labels Oct 16, 2026
@clawsweeper

clawsweeper Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge.

What this changes

Adds a default-off setting that merges labelled OpenCode OpenAI usage into ambient Codex history.

Example: OpenCode records 1,545 OpenAI tokens today with a recorded cost of $0.

  • Before: Codex local history excludes those tokens.
  • After: Enabling the setting adds a labelled OpenCode row with 1,545 tokens and preserves the recorded $0.

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) Useful CLI and upgrade evidence supports the feature, but two source-reproducible defects block merge and the latest menu behavior remains unproven.
Proof confidence 🦐 gold shrimp (3/6) Real behavior proof is necessary before merge. See Before merge.
Patch quality 🦪 silver shellfish (2/6) 2 actionable review findings remain.

Product

Kind: Feature · Worth it: Yes · Fix scope: Complete
User problem: People using OpenCode with OpenAI cannot see that activity in ambient Codex local history.
Reason: The linked owner discussion welcomes the reader direction and establishes the labelled, opt-in, permanently unscoped boundary followed here.

Merge readiness

⛔ Blocked before merge - 3 items remain

Keep open: this PR adds useful missing coverage, but the menu activation repair is incomplete and invalid legacy records can hide valid migrated usage.

Priority: P2
Reviewed head: 78336c31101786a4bdacfca034770beee2f0c14f

Before merge

  • Add real behavior proof - The supplied branch-built CLI transcripts demonstrate import on/off, 2,345 labelled tokens, scoped exclusion, grouped accounting, provenance, and stable-state compatibility. The subsequent menu-lane activation change has no app runtime evidence: provide a screenshot or recording from a freshly built bundle showing that the OpenCode toggle alone displays history while the older cost toggles remain off, with sensitive details redacted. The inspected downloaded images show the separate OpenCode Go PR and do not cover this menu change. After adding proof, update the PR body, then request a new review with @clawsweeper re-review. The PR author or someone with repository write access can request it.
  • Use the history lane gate for menu visibility (P2) - With costUsageEnabled and codexLocalSessionCostLedgerEnabled both false, enabling only codexIncludeOpenCodeLocalHistory now starts scanning, but the menu still receives false for both display flags. costSummaryShowsInline uses only the older toggles, and costSummaryShowsSubmenu still calls isCostUsageEffectivelyEnabled. Consequently resolveInlineUsageDashboard returns nil and tokenUsageSection is disabled, so the targeted OpenCode-only user still sees no history in the menu. Make both menu visibility predicates use the history lane gate while preserving the selected display style and the separate Spend Dashboard gate, and cover the constructed menu model.
  • Deduplicate against usable legacy usage (P2) - The raw message-ID exclusion drops a valid v2 request whenever any legacy row shares its ID, even if that legacy row contributes no usable usage. For example, an openai assistant legacy row with cost -3 is rejected by readRows, while its valid same-ID v2 row with cost 3 and recorded tokens is excluded here, losing the request entirely. The concrete invalid-copy scenario is independently documented in Fix OpenCode Go v2 local history gaps #4587. Exclude v2 rows only when usable legacy usage actually owns the ID, preserving valid step-finish precedence and known-zero costs, and add a regression for the invalid legacy copy.

Findings

  • [P2] Use the history lane gate for menu visibility — Sources/CodexBar/UsageStore+MenuCardModel.swift:146-149
  • [P2] Deduplicate against usable legacy usage — Sources/CodexBarCore/Providers/OpenCode/OpenCodeLocalUsageReader.swift:283-284

Tests

  • Missing end-to-end proof: Show the freshly built app with both older cost toggles off, enabling only OpenCode history and displaying its menu rows; the current settings tests do not exercise menu construction or demonstrate a base-failing regression.
Agent review details

How this fits together

The local-history reader extracts usage metadata from OpenCode SQLite and feeds Codex cost aggregation, which publishes menu and CLI history while excluding scoped accounts and the Spend Dashboard.

flowchart TD
  A[OpenCode SQLite] --> B[Read only usage reader]
  C[Default off setting] --> D[Ambient scope gate]
  B --> D
  D --> E[Codex history aggregation]
  F[Native Codex history] --> E
  E --> G[Menu and CLI]
Loading

Technical review

Best possible solution:

Complete ambient menu visibility and deduplicate against usable legacy usage while preserving recorded costs and account isolation.

Do we have a high-confidence way to reproduce the issue?

Source inspection establishes both failures: OpenCode-only activation leaves both menu display predicates false, and a negative-cost legacy row suppresses a valid same-ID v2 row; no target code was executed.

Is this the best way to solve the issue?

The opt-in ambient source follows the established cross-client boundary, but activation must reach the actual menu presentation predicates and deduplication must preserve valid replacement records.

Full review comments:

  • [P2] Use the history lane gate for menu visibility — Sources/CodexBar/UsageStore+MenuCardModel.swift:146-149
    With costUsageEnabled and codexLocalSessionCostLedgerEnabled both false, enabling only codexIncludeOpenCodeLocalHistory now starts scanning, but the menu still receives false for both display flags. costSummaryShowsInline uses only the older toggles, and costSummaryShowsSubmenu still calls isCostUsageEffectivelyEnabled. Consequently resolveInlineUsageDashboard returns nil and tokenUsageSection is disabled, so the targeted OpenCode-only user still sees no history in the menu. Make both menu visibility predicates use the history lane gate while preserving the selected display style and the separate Spend Dashboard gate, and cover the constructed menu model.
    Confidence: 0.99
  • [P2] Deduplicate against usable legacy usage — Sources/CodexBarCore/Providers/OpenCode/OpenCodeLocalUsageReader.swift:283-284
    The raw message-ID exclusion drops a valid v2 request whenever any legacy row shares its ID, even if that legacy row contributes no usable usage. For example, an openai assistant legacy row with cost -3 is rejected by readRows, while its valid same-ID v2 row with cost 3 and recorded tokens is excluded here, losing the request entirely. The concrete invalid-copy scenario is independently documented in Fix OpenCode Go v2 local history gaps #4587. Exclude v2 rows only when usable legacy usage actually owns the ID, preserving valid step-finish precedence and known-zero costs, and add a regression for the invalid legacy copy.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 0ff1cf55c0a2.

Provenance checked

  • Ambient versus account-owned history keeps the original intent (81c8a1c: The Pi integration keeps cross-client inclusive history ambient and account-scoped Codex history native.)
  • Cost provenance and serialization changes intended behavior with a stated reason (39b17af: Spend reporting distinguishes list-price estimates from recorded metered amounts and preserves window provenance.)
  • Codex cost disclosure changes intended behavior with a stated reason (Codex: distinguish API estimates from subscription bills #2181: Local token-derived dollars must be disclosed as API-equivalent estimates rather than subscription bills.)
  • Menu summary visibility keeps the original intent (61c7e1c: Presentation follows the selected summary style while allowing the local Codex ledger to activate independently.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

Acceptance criteria:

  • swift test --filter OpenCodeLocalHistorySettingsTests
  • swift test --filter OpenCodeLocalUsageReaderTests
  • swift test --filter OpenCodeLocalHistoryMergeTests
  • swift test --filter ProviderArchitectureGatekeeperTests
  • make check

What I checked:

Likely related people:

  • Peter Steinberger: Raw commit 61c7e1c adds Sources/CodexBar/SettingsStore+TokenCost.swift:5 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 61c7e1ce821a; files: Sources/CodexBar/SettingsStore+TokenCost.swift)
  • Yuxin Qiao: Raw commit 39b17af adds Sources/CodexBarCore/CostProvenance.swift:4 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 39b17af0b75a; files: Sources/CodexBarCore/CostProvenance.swift)

Review metrics

Metric Value Why it matters
Change size Production +739/-49 lines; tests +1347/-11 lines Most test growth covers SQLite fixtures, source boundaries, and serialization compatibility; menu construction still needs coverage beyond the settings predicate.

Labels

Label changes:

  • add P2: The remaining defects affect visibility and completeness of an optional local-history source with limited blast radius.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦐 gold shrimp and patch quality is 🦪 silver shellfish.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask.
  • remove proof: sufficient: Current real behavior proof status is insufficient, not sufficient.
  • remove P3: Current review triage priority is P2, so this older priority label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 📣 needs proof.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🦪 silver shellfish, so this older rating label is no longer current.

Label justifications:

  • P2: The remaining defects affect visibility and completeness of an optional local-history source with limited blast radius.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦐 gold shrimp and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

History

Review history (4 earlier review cycles)
  • reviewed 2026-10-16T07:08:58.490Z sha 55b2800 :: needs real behavior proof before merge. :: [P2] Normalize the lower bound to the reporting day's start | [P2] Bucket OpenCode rows with the supplied reporting calendar | [P2] Honor OpenCode's XDG data directory | [P2] Exclude environment-selected Codex homes from the merge
  • reviewed 2026-10-16T10:08:55.488Z sha 8065b8c :: needs real behavior proof before merge. :: [P2] Carry OpenCode usage into the grouped accounting boundary | [P2] Preserve recorded-cost provenance in the merged snapshot | [P2] Select only the database OpenCode actually uses | [P3] Pin the calendar in the earlier-today regression test | [P3] Close the SQLite handle when opening fails | [P3] Update the provider guides that still deny this capability
  • reviewed 2026-10-16T12:48:29.812Z sha a4294d9 :: needs real behavior proof before merge. :: [P2] Capture the SQLite error before closing the connection | [P2] Encode the recorded-cost marker | [P2] Finish provenance-aware disclosure in the default views
  • reviewed 2026-10-16T13:21:00.093Z sha 3655259 :: blocked before merge. :: none

Reviewed October 16, 2026, 12:03 PM ET / 16:03 UTC (Revision 5).

- Normalize the reader's lower bound to the reporting day's start so
  earlier-today turns survive the now-anchored foreground window.
- Key day buckets with the supplied reporting calendar instead of the
  device calendar.
- Resolve the database root through XDG_DATA_HOME (absolute values only,
  matching xdg-basedir) before falling back to ~/.local/share.
- Exclude the merge whenever CODEX_HOME selects a Codex home, mirroring
  the codexHomePath argument at both merge points.
- Label merged models "(OpenCode local)" so the source stays visible in
  UI breakdowns and CLI JSON.
- Drop the unused scope fingerprint machinery and its low-value test;
  cover the new boundaries with behavior tests instead.
@Danielweng0902

Danielweng0902 commented Oct 16, 2026 •

Copy link
Copy Markdown
Author

@clawsweeper Thanks for the thorough review — all four boundary findings were real, and the provenance concern was the right call. Pushed in 8065b8c ("Harden OpenCode local history merge per review"), point by point:

1. Time boundary normalization — dailyEntries now normalizes the lower bound with calendar.startOfDay(for: since), so the now-anchored foreground window no longer drops earlier-today turns while keeping same-day later ones. Covered by earlier today rows survive a clock time lower bound.

2. Reporting-calendar day keys — day keys now come from CostUsageDayRange.dayKey(from:calendar:) with the supplied calendar, matching the reporting time zone everywhere else. Covered by day keys follow the supplied reporting calendar (a 00:30 UTC row keys to its UTC day under a UTC reporting calendar).

3. XDG_DATA_HOME — the data root now resolves the way OpenCode's xdg-basedir dependency does: an absolute XDG_DATA_HOME wins; relative values are ignored per the XDG spec and fall back to ~/.local/share. Covered by xdg data home relocates the database discovery (absolute honored, relative ignored).

4. CODEX_HOME exclusion — both merge points now gate on a shared codexHistoryIsAmbient(codexHomePath:environment:) helper: the merge is excluded whenever a Codex home was explicitly selected, whether through the codexHomePath argument or a non-empty CODEX_HOME in the process environment (the same environment the native scanner resolves it from). Covered for the foreground scan and the cached publication (environment selected codex home never merges opencode local history, cached publication honors environment selected codex homes).

5. Visible provenance — adopted your recommendation: merged model names carry an (OpenCode local) suffix, so rows stay identifiable in UI model breakdowns and CLI JSON rather than silently blending into native totals. Native gpt-5.1-codex and OpenCode gpt-5.1-codex (OpenCode local) aggregate separately.

6. Scope-fingerprint test — removed. On reflection the whole scope/fingerprint plumbing was write-only output (nothing consumed it), so the machinery is gone too; loadDailyReport returns the report directly and the path-resolution test now asserts rows flow through the override.

Real-behavior proof

Built CodexBarCLI from the branch. Synthetic OpenCode database: two openai turns (1,545 tokens today 10:00, 800 tokens yesterday 08:00 — both cost $0 as OpenCode records subscription usage) plus one anthropic/claude-opus row that must never be selected. Machine also has real native Codex usage and a real OpenCode database.

Default (setting off):

$ OPENCODE_DB=/tmp/opencode-proof/opencode.db codexbar cost --provider codex --days 2 --format json
# daily model breakdowns: gpt-6-astra, gpt-6.1-sol, codex-auto-review  ← no OpenCode rows

Setting on, ambient home:

$ OPENCODE_DB=/tmp/opencode-proof/opencode.db codexbar cost --provider codex --days 2 --format json
# "date": "2026-10-15" → {"cost": 0, "modelName": "gpt-5.1-codex (OpenCode local)", "totalTokens": 800}
# "date": "2026-10-16" → {"cost": 0, "modelName": "gpt-5.1-codex (OpenCode local)", "totalTokens": 1545}
# claude-opus row: absent

Under --days 1 (window anchored at now = 15:54), the 10:00 turn still appears on today's entry — the start-of-day normalization from fix 1. The yesterday-08:00 turn correctly needs --days 2 and lands on its own calendar day.

Setting on, CODEX_HOME scoped to an empty directory:

$ OPENCODE_DB=/tmp/opencode-proof/opencode.db CODEX_HOME=/tmp/opencode-proof/empty-codex-home \
    codexbar cost --provider codex --days 2
Codex API-equivalent estimate (not billed)
Today: $0.00 · 0 tokens
Last 2 days: $0.00 · 0 tokens

Zero rows — empty native home and the machine-wide OpenCode merge excluded, exactly the promised scoped-home behavior.

Setting on, XDG_DATA_HOME relocation (no OPENCODE_DB override):

$ XDG_DATA_HOME=/tmp/opencode-proof/xdg-data codexbar cost --provider codex --days 2 --format json
# 2026-10-15 → gpt-5.1-codex (OpenCode local) · 800 tokens
# 2026-10-16 → gpt-5.1-codex (OpenCode local) · 1545 tokens
$ XDG_DATA_HOME=relative/path …   # ignored per the XDG spec; no rows from the relative path

Tests: reader (16) + merge (8) suites pass, plus ProviderArchitectureGatekeeperTests/fetcher/scanner suites (296) and the settings/catch-up/CLI suites (31); SpendDashboard suites pass in isolation (they time out only when the full test plan runs in parallel on this machine). The synthetic-database setup script and raw transcripts can be attached on request.

@Danielweng0902

Copy link
Copy Markdown
Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:feature Preliminary submission classification; full source review pending. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. clawsweeper:intake:feature Preliminary submission classification; full source review pending. labels Oct 16, 2026
Review follow-up for the recorded-cost contract:

- Grouped accounting: OpenCode rows join the unattributed project
  bucket (the Pi treatment) in both the foreground scan and the cached
  publication; session text views stay native with an explicit
  warning, matching the Pi session-view precedent.
- Recorded-cost provenance: merged rows mark their entries, snapshots
  report sourceRecorded or mixed instead of a pure list-price claim,
  and the CLI title/hint follow. Spend Dashboard text maps the new
  case conservatively; it never receives these rows.
- Database selection: OPENCODE_DB selects exactly one database
  (:memory: reads as no history, never a substitute) and the release
  database is the default without channel fallbacks, matching
  upstream's contract. OPENCODE_UPDATE_CHANNEL is no longer consulted.
- Reader hygiene: close the SQLite handle when sqlite3_open_v2 fails.
- Tests: pinned-calendar regression for the earlier-today bound,
  selection-contract coverage, mixed-history provenance and grouped
  accounting, cached-publication parity, and legacy-entry decoding.
- Docs: codex.md and opencode.md now describe the opt-in toggle and
  its ambient-only boundary instead of denying the capability.
@Danielweng0902

Copy link
Copy Markdown
Author

@clawsweeper Thanks for the deeper pass — all eight items are addressed in a4294d9 ("Harden OpenCode local merge boundaries and provenance"), point by point:

1. Grouped accounting boundary (P2) — both merge sites now apply the established Pi treatment: unknownProjectBreakdown(from:) joins the OpenCode rows into the unattributed project bucket (foreground scan and cached publication), and inclusive session breakdowns are cleared because they cannot attribute these rows. Session text views keep the Pi precedent of a separate native-only load with an explicit omission warning; JSON and project grouping keep the merged rows.

2. Recorded-cost provenance (P2) — merged entries now carry a costIsSourceRecorded marker (persisted through caches, OR'd across same-day merges, older caches decode it as false). Snapshots report sourceRecorded when only OpenCode rows exist and mixed when estimated rows coexist; window narrowing keeps mixed windows mixed instead of collapsing to listPriceEstimate. The Codex CLI title becomes "API-equivalent estimate + source-recorded cost (not billed)", the hint names the recorded OpenCode costs, and JSON provenance follows. Subscription zeroes are still never repriced. The Spend Dashboard maps the new case conservatively but never receives these rows by design.

3. Database selection contract (P2) — rewrote candidateDatabaseURLs to upstream's single-path contract: OPENCODE_DB selects exactly one database (:memory: reads as no on-disk history, never a substitute; absolute as-is; relative inside the data directory), and without an override the release database under the XDG data root is the selection — no channel fallback, and OPENCODE_UPDATE_CHANNEL is no longer consulted since upstream derives the channel from the build's InstallationChannel, which an external reader cannot observe. Non-release channel databases stay reachable through OPENCODE_DB, exactly like pointing OpenCode itself at them.

4. Pinned calendar in the earlier-today test (P3) — you were right, the fixed timestamp was 2026-03-06 12:00 UTC, not the local afternoon the comment claimed (it only passed in UTC+8 hosts). The test now pins a UTC calendar and constructs both instants from DateComponents in that calendar.

5. SQLite handle on open failure (P3) — the failure branch closes the handle before throwing, as the Go reader does.

6. Contradictory provider guides (P3) — docs/codex.md's coverage table and docs/opencode.md's import paragraph now describe the opt-in toggle and its ambient-only boundary; docs/providers.md states the new selection contract and provenance behavior.

7. Data-model compatibility proof — entries persisted before this feature decode without the recorded marker decodes a pre-feature entry JSON (no costIsSourceRecorded, no coverage counts) and round-trips it; the cached-publication parity tests replay caches across the merge. Older caches decode the new field as false, so stored history keeps loading.

8. Persisted-setting fresh-install/upgrade proof — terminal verification below: toggling the key on/off flips the merge both directions while an unrelated preference in the same domain survives.

Real-behavior proof (branch-built CLI, synthetic DB with 1,545 + 800 token $0 rows plus a never-selected claude-opus row)

Default off → on, ambient, grouped accounting + provenance:

$ codexbar cost --provider codex --days 2 --group-by project        # setting off
Codex API-equivalent estimate (not billed)
Projects (Last 2 days): …native projects only…

$ defaults write com.steipete.codexbar codexIncludeOpenCodeLocalHistory -bool YES
$ codexbar cost --provider codex --days 2 --group-by project
Codex API-equivalent estimate + source-recorded cost (not billed)
Projects (Last 2 days): …native projects…
Unknown project: $0.00 · 2,345 tokens          ← the unattributed bucket (800 + 1,545)

$ codexbar cost --provider codex --days 2 --format json
"provenance": "mixed"
2026-10-15 → gpt-5.1-codex (OpenCode local) · 800 tokens · cost 0
2026-10-16 → gpt-5.1-codex (OpenCode local) · 1,545 tokens · cost 0
projects: […, ("Unknown project", 2345)]

Session view stays native with an explicit warning:

$ codexbar cost --provider codex --days 2 --group-by session
Codex API-equivalent estimate (not billed)
Conversations (Last 2 days): …native sessions only…
Warning: Session grouping shows native Codex conversations only; Pi/OMP usage is omitted from this view. …
Warning: Session grouping shows native Codex conversations only; OpenCode local history is omitted from this view. …

Scoped home still excludes everything:

$ CODEX_HOME=<empty dir> codexbar cost --provider codex --days 2 --group-by project
Codex API-equivalent estimate (not billed)
Projects (Last 2 days): —

Setting persistence (item 8):

$ defaults write com.steipete.codexbar proofUnrelatedPref -string "keep-me"
$ … cost --format json → provenance: mixed · Unknown project tokens: 2345
$ defaults delete com.steipete.codexbar codexIncludeOpenCodeLocalHistory
$ … cost --format json → provenance: listPriceEstimate · Unknown project: absent
$ defaults read com.steipete.codexbar proofUnrelatedPref → keep-me

Tests: reader (17) + merge (10) suites, gatekeeper/fetcher/scanner/PiNative/calendar suites (333), CLI/settings/catch-up suites (65), and SpendDashboard/window/coverage suites (77) all pass; SpendDashboard suites time out only when the full plan runs in parallel on this machine.

@Danielweng0902

Copy link
Copy Markdown
Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:feature Preliminary submission classification; full source review pending. and removed clawsweeper:intake:feature Preliminary submission classification; full source review pending. labels Oct 16, 2026
Third review round:

- Capture the SQLite failure (which reads errcode/errmsg through the
  handle) before closing it on open failures, avoiding use-after-free.
- Encode costIsSourceRecorded so recorded entries round-trip through
  caches; the legacy-decoding test now also round-trips a true entry.
- Pass provenance through the default CLI cost footer and the menu
  usage hints, so recorded OpenCode rows are never described as
  computed list-price estimates in any view.
@Danielweng0902

Copy link
Copy Markdown
Author

@clawsweeper Third round addressed in 3655259 ("Finish provenance disclosure and harden failure paths") — thanks again, all three P2s were real:

1. Capture the SQLite error before closing the connection (P2) — correct catch: the failure now materializes first (sqliteFailure reads sqlite3_errcode/sqlite3_errmsg through the still-open handle), then the handle is closed, then the captured failure throws. No use-after-free on a directory-as-OPENCODE_DB, unreadable database, or removal race.

2. Encode the recorded-cost marker (P2) — Entry.encode(to:) now writes costIsSourceRecorded when true (native entries keep their legacy shape). The serialization test round-trips a true entry and asserts both the JSON payload ("costIsSourceRecorded":true) and decode equality, in addition to the legacy-false case.

3. Provenance-aware disclosure in the default views (P2) — the default codexbar cost footer now passes snapshot.costProvenance (the call site my earlier sweep missed), and the menu usage hints (MenuCardView+Costs, both inline dashboard call sites) consult the snapshot's provenance: recorded/mixed Codex history shows "Source-recorded cost from OpenCode local history · not a subscription bill" / "Estimated from token usage plus source-recorded OpenCode cost · not a subscription bill" instead of the fixed estimate hint.

Upgrade proof (latest-stable → branch, existing settings and history)

The branch-built CLI running against this machine's real installed-app state — existing com.steipete.codexbar preferences, shared ~/.codexbar caches and config, and real native Codex history:

$ defaults read com.steipete.codexbar | grep -c "="        # before
59
$ defaults read com.steipete.codexbar codexIncludeOpenCodeLocalHistory
Error: Could not find key 'codexIncludeOpenCodeLocalHistory' in domain 'com.steipete.codexbar'.

$ codexbar cost --provider codex --days 2                  # branch build, no overrides
Codex API-equivalent estimate (not billed)
Today: $196.66 · 310M tokens
Last 2 days: $270.34 · 428M tokens                          ← existing history loads

$ defaults write com.steipete.codexbar codexIncludeOpenCodeLocalHistory -bool YES
$ OPENCODE_DB=<synthetic> codexbar cost --provider codex --days 2
Codex API-equivalent estimate + source-recorded cost (not billed)
Today: $196.66 · 310M tokens
Last 2 days: $270.34 · 428M tokens                          ← same history, OpenCode rows merged (2,345 tokens / $0, below display rounding)
Not a subscription bill or plan value · local usage × public API prices + source-recorded OpenCode cost

$ defaults read com.steipete.codexbar | grep -c "="        # after enabling
60                                                          ← exactly one new key
$ defaults delete com.steipete.codexbar codexIncludeOpenCodeLocalHistory
$ defaults read com.steipete.codexbar | grep -c "="
59                                                          ← restored

The shared ~/.codexbar caches (written by the latest stable app/CLI) load under the branch build for every command above — that is the data-model compatibility demonstration for existing stored data, alongside the legacy-entry decode/round-trip tests.

Tests: reader (17) + merge (10), gatekeeper, CLI cost, fetcher/scanner/PiNative/calendar (105), menu/dashboard (80), and catch-up/settings suites all pass locally.

@Danielweng0902

Copy link
Copy Markdown
Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:feature Preliminary submission classification; full source review pending. proof: sufficient Contributor real behavior proof is sufficient. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Oct 16, 2026
@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 869f746844

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +142 to 146
tokenCostUsageEnabled: self.settings.isHistoryLaneEffectivelyEnabled(for: provider),
tokenCostIsRefreshing: !isSettings && self.tokenCostRefreshIsActive(for: provider),
codexLocalSessionCostLedgerEnabled: self.settings.codexLocalSessionCostLedgerEnabled,
// Settings exposes available costs regardless of the menu's display style.
costSummaryInlineEnabled: isSettings || self.settings.costSummaryShowsInline(for: provider),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Activate the inline view from the OpenCode opt-in

When both older cost toggles are off and a user has retained the inlineSummary display style (for example, by selecting Inline and later turning cost tracking Off), enabling OpenCode makes tokenCostUsageEnabled true here, but costSummaryShowsInline still returns false because it checks only those older toggles, while .inlineSummary also disables the submenu. The still-unwired inline path is fresh evidence beyond the earlier lane-activation fix: the scan runs but neither cost presentation renders, so the opt-in appears to do nothing; the inline gate should use the new history-lane effective state too.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Verified real and fixed in 7d573f9 ("Complete the inline lane and ground mixed provenance on real cost"):

  • Inline lane: costSummaryShowsInline now honors the OpenCode opt-in in the same codex branch as the ledger, so the inline summary — the only presentation under .inlineSummary, which disables the submenu — renders from the opt-in alone. New test inline summary style renders from the opencode opt-in alone pins the exact scenario (style retained, both older toggles off): inline enabled, submenu correctly off per the style, menuCardInput(.codex, .menu).costSummaryInlineEnabled true, Claude untouched.
  • Mixed provenance: both predicates (scan + cached path at 1572) now require a native row with an actual costUSD rather than merely any native row, matching the existing cursorCostProvenance idiom. New test unpriced native history keeps provenance source recorded pins it: unknown-model native session (tokens only) + recorded OpenCode cost → tokens still merge but provenance stays .sourceRecorded; the existing priced-native .mixed test passes unchanged.

Gatekeeper and related suites pass locally (the two parallel-load-flaky dashboard timing tests fail identically on the base commit and pass in isolation).

calendar: options.scanOptions.calendar)
}
var openCodeLocalReport: CostUsageDailyReport?
let listPriceHistoryPresent = !daily.data.isEmpty

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Base mixed provenance on an actual list-price cost

When native Codex/Pi history is present but fully unpriced (for example, an unknown model leaves every native costUSD nil) and OpenCode contributes the only recorded cost, this nonempty-data check still selects .mixed. The menu and CLI then claim an API-price estimate is included even though no list-price amount exists; the cached path repeats the same predicate at line 1572. Both paths should test for an actual native numeric cost rather than merely any native row.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 7d573f9 — the provenance predicate on this line (and its cached-path twin at 1572) now tests for a native row with an actual costUSD instead of any native row, mirroring the existing cursorCostProvenance idiom. unpriced native history keeps provenance source recorded covers the exact scenario: unknown-model native tokens + recorded OpenCode cost stays .sourceRecorded; priced native history still yields .mixed.

Both follow the Codex review of 869f746:

costSummaryShowsInline still consulted only the older toggles, so a
user retaining the inlineSummary style (which disables the submenu)
saw no cost presentation at all from the OpenCode opt-in. The codex
branch now honors the opt-in like the ledger, so the inline summary
renders on its own.

The mixed-provenance predicates tested for any native row, so fully
unpriced native history (unknown model) plus recorded OpenCode cost
claimed a list-price estimate that was not included. Both the scan
and cached paths now require a native row with an actual cost,
matching the existing cursorCostProvenance idiom.

Adds the inline-style and unpriced-native behavior tests.
@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d573f97ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +25 to +26
self.isCostUsageEffectivelyEnabled(for: provider) ||
(provider == .codex && self.codexIncludeOpenCodeLocalHistory)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Select the ambient scope for the OpenCode-only lane

When a managed-account or profile-home Codex source is active while both older cost toggles are off, this unconditional setting check enables history refreshes, but UsageStore.tokenCostScope still returns that scoped home and shouldIncludeOpenCodeLocalHistoryInTokenSnapshot consequently returns false. The fresh scoped-source path was not addressed by the earlier ambient-lane fix: enabling “Include OpenCode…” scans the scoped native ledger and never publishes the OpenCode-only account-agnostic section, so the opt-in appears ineffective; make this opt-in select the codex:ambient scope (or keep the lane disabled unless that scope is used).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Verified real and fixed in ad31248 ("Select the ambient scope for the OpenCode lane and split window cost kinds"):

The opt-in now selects the codex:ambient scope in tokenCostScope, exactly mirroring the line four rows above where codexLocalSessionCostLedgerEnabled has always forced ambient — machine-local history reads the machine-local home. With a managed source active, shouldIncludeOpenCodeLocalHistoryInTokenSnapshot now opens (ambient home → nil path → setting) instead of silently scanning a scoped ledger the merge can never touch. New test opencode opt-in selects the ambient scope over the managed account home pins: default-off keeps the managed home and closed gate; opt-in alone switches to codex:ambient and opens the merge.

.unknown
}
if includesMetered { return hasWindowCosts ? .mixed : .vendorMetered }
if includesSourceRecorded { return hasWindowCosts ? .mixed : .sourceRecorded }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Separate source-recorded cost when narrowing mixed windows

When a mixed 30-day snapshot has list-priced native cost only on an older day and today's window has only an OpenCode row, hasWindowCosts is true because of the OpenCode cost itself and includesSourceRecorded is also true, so this branch returns .mixed. The fresh narrow-window path remains after the snapshot-level predicate fix: summary(forLastDays: 1) and CLI JSON claim a list-price estimate is present when the selected window contains only source-recorded cost; track an independent list-priced-cost bit for the selected entries before returning .mixed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Verified real and fixed in ad31248 — window summaries now carry an independent list-priced bit:

  • forWindow gained hasListPricedCosts (optional, legacy callers keep the old approximation) and the .mixed/sourceRecorded branch uses it instead of the any-cost bit.
  • summary(forLastDays:) passes entries.contains { $0.containsListPricedCost || ($0.costUSD != nil && !$0.costIsSourceRecorded) }.
  • Because a merged day folds recorded and priced rows into one entry whose recorded marker only says some cost is recorded, CostUsageDailyReport.Entry now persists containsListPricedCost — set only for genuinely mixed days so single-report merges stay identity-preserving (CostUsageDailyReportMergeTests unchanged) and older caches decode as false.

New test narrow window of a mixed snapshot stays source recorded pins the exact scenario: priced native row two days older + OpenCode row today → snapshot .mixed, 7-day window .mixed, 1-day window .sourceRecorded.

…kinds

Both follow the Codex review of 7d573f9:

The OpenCode opt-in enabled the history lane but left a managed-account
or profile-home source selecting its scoped home, so the merge gate
stayed closed and the toggle appeared ineffective. The opt-in now
selects the codex:ambient scope exactly like the local ledger always
has — machine-local history reads the machine-local home.

forWindow treated any window cost as a list-price cost, so a mixed
snapshot's narrow window that held only an OpenCode row still claimed
a mixed estimate. Window summaries now carry a list-priced bit, and
merged days persist a containsListPricedCost flag for the one case the
recorded marker alone mislabels — a day mixing recorded and priced
cost. Pure days keep the flag off so single-report merges stay
identity-preserving.

Adds the ambient-scope and narrow-window behavior tests.
@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad31248d95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// ambient history, so either selects the ambient home like the ledger always has;
// without this the opt-in would scan a managed home whose scoped rows can never
// include OpenCode history, leaving the toggle ineffective (#3273 contract).
if self.settings.codexLocalSessionCostLedgerEnabled || self.settings.codexIncludeOpenCodeLocalHistory {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve ambient history across Codex account switches

When the OpenCode opt-in is on, this branch makes the cost scope account-agnostic, but accountScopedTokenSnapshot still bypasses provider-config matching only for codexLocalSessionCostLedgerEnabled. Switching the active managed/profile account increments the Codex config revision while this scope stays codex:ambient, so the existing valid publication is rejected and the This Mac cost section and submenu disappear until a potentially long rescan completes. Accept an ambient publication when its scope signature is still current, without accepting a pre-toggle scoped snapshot.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Verified real and fixed in 29eeef6 ("Keep ambient history across account switches and label it everywhere"):

accountScopedTokenSnapshot now has an explicit opt-in branch: an ambient publication is accepted across provider-config churn (account switches) as long as its scope signature still matches the current one — ambient history is account-agnostic, so rejecting it only made the This Mac section vanish behind a rescan. A pre-toggle scoped publication is rejected by that same signature check, so account rows never leak into the machine-local lane. The ledger branch keeps its existing behavior untouched.

New test ambient opencode publication survives codex account switches pins all three: ambient publication survives a config-revision bump; a publication installed under a managed home returns nil once the opt-in selects ambient.

"Merges machine-local OpenCode usage for OpenAI-provider assistant records from ~/.local/share/opencode/opencode.db into this Mac's ambient Codex history.",
"Reads usage metadata only (read-only SQLite: model, tokens, recorded cost); prompts, responses and auth state are never read.",
"OpenCode records $0 cost for subscription usage; recorded dollars are shown as-is and never treated as billing.",
"Rows are attributed to the model only, never to an account, and are excluded from managed accounts, scoped CODEX_HOME scans and the Spend Dashboard.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Label OpenCode history as machine-wide in provider settings

When this opt-in is the only local toggle and a Codex account overview is present, the selected cost scope is codex:ambient, yet PreferencesProviderDetailView chooses its heading and footer only from codexLocalSessionCostLedgerEnabled; it therefore labels these machine-wide rows as “Local usage comes from the current Codex profile.” That contradicts this never-account-attributed contract and can make recorded costs look owned by the selected managed/profile account, so the label should use the effective ambient scope.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 29eeef6 — PreferencesProviderDetailView now picks the "This Mac" heading and "shared across accounts" footer from codexLocalSessionCostLedgerEnabled || codexIncludeOpenCodeLocalHistory, so machine-wide rows are never labeled as the selected profile's while the opt-in selects the ambient scope.

/// Window mixes list-price rows with vendor-metered rows.
/// Cost recorded by the source client (for example OpenCode's local history) and
/// surfaced as-is; no list-price estimation was applied to these rows.
case sourceRecorded

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Carry source provenance into the cost-history chart

When a .sourceRecorded snapshot is opened through the Cost submenu, CostHistoryChartMenuView receives the daily amounts but not snapshot.costProvenance, so it still renders an “Est. total” and the static Codex “Estimated from token usage” disclaimer. This presents OpenCode's recorded dollars as list-price estimates despite the new provenance case; pass provenance through appendCostHistoryChartItem and choose the chart label and disclaimer from it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 29eeef6 — appendCostHistoryChartItem passes tokenSnapshot.costProvenance into CostHistoryChartMenuView:

  • .sourceRecorded snapshots render "Recorded total/subtotal (%@): %@" instead of "Est. …" (new keys localized across all 23 catalogs).
  • The disclaimer reuses the menu-card provenance strings: "Source-recorded cost from OpenCode local history · not a subscription bill" for recorded-only, "Estimated from token usage plus source-recorded OpenCode cost · …" for mixed; every other case keeps the existing estimate disclaimer.

Gatekeeper (marker-justified), localization catalog parity, and the related suites all pass locally.

All three follow the Codex review of ad31248:

accountScopedTokenSnapshot bypassed config matching only for the local
ledger, so with the OpenCode opt-in active an account switch rejected
the still-valid ambient publication and the This Mac section vanished
behind a rescan. The opt-in now accepts an ambient publication across
config-revision churn while a pre-toggle scoped publication is still
rejected by scope signature.

PreferencesProviderDetailView picked its This Mac heading and footer
only from the ledger toggle, mislabeling machine-wide rows as the
current Codex profile's; it now follows the effective ambient scope.

CostHistoryChartMenuView ignored snapshot provenance, so a
source-recorded snapshot rendered an Est. total and the static
estimate disclaimer. The chart now receives costProvenance and picks
the Recorded total label and the source-recorded disclaimer, with the
new keys localized across all 23 catalogs.

Adds the ambient-publication survival test.
@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Danielweng0902

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to sample tokens
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@steipete

Copy link
Copy Markdown
Owner

Thanks @Danielweng0902 for the opt-in OpenCode history work and the account-scope coverage, and for iterating on it so many times. We're going to close this implementation, though, for a structural reason rather than size: the separate reader loads all matching history before windowing, and cached Codex publication runs that scan again, which breaks the memory and I/O budgets we recently tightened for the cost ledger. We tried a compact version on top of the shared reader, but two histories sharing Go's 4 MiB cache evict each other and force rescans of unchanged data.

The right shape is the shared incremental history path (bounded memory, cache-only publication) with a single off-by-default setting on top. If you'd like to take that on, we'd welcome it; #3273 remains the tracking issue, and labelled list-price estimates are still a valid direction there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: include OpenCode sessions in Codex usage tracking

2 participants