fix(codex): attribute OpenCode 2 ChatGPT OAuth usage - #1284
Conversation
OpenCode 2 writes sessions to session_message and OAuth to the credential table, so Codex Today stayed empty after the upgrade while the v1 message table no longer received new rows.
The OAuth-creation cutoff only filters OpenCode 2 session_message rows. Pre-upgrade message-table history still counts.
A leftover opencode-next.db sorted before opencode.db and could win the OAuth check. Rank current rows globally, and do not fall back to a stale auth.json when a credential query fails.
Keep credential ranking fields as Double so an out-of-range time_updated cannot trap on Int conversion. Move the OAuth-creation bound into the v2 SQL branch, drop the source column and Swift post-filter, and collapse the schema probe into the scanner so OpenCodePaths is paths-only. Tests share one store factory and match exact SQL instead of substrings.
|
@pullfrog review this |
There was a problem hiding this comment.
Important
Two credential-attribution cases need fixing: an empty OpenCode 2 credential table revives stale file-based OAuth, and one channel's credential controls every channel's usage.
Reviewed changes Reviewed the OpenCode 2 Codex-attribution implementation, related tests, and provider documentation.
- Credential lookup: Database-first OpenAI credential discovery, cross-channel ranking, and failure handling.
- Usage scanning: Schema detection, v1/v2 message queries, completion filters, and the v2 OAuth creation-time boundary.
- Regression coverage and docs: Updated test doubles, credential/scanner assertions, and documented attribution behavior.
Validated both findings with an isolated Swift harness compiling the production OpenCodeAuthStore.swift against real in-memory SQLite fixtures, and checked the upstream credential lifecycle. The full macOS test suite was not run on this Linux runner.
GPT Astra | 𝕏
OpenCode keeps a separate credential store per release-channel database, so one globally ranked credential let a newer stable API key hide preview OAuth usage and applied one channel's login bound to every channel. The scanner now asks the auth store for each database's own credential. An existing but empty credential table now reads as logged out: OpenCode 2 logout deletes the row and leaves the imported auth.json behind, which previously revived OAuth with no login bound. auth.json stands in only for OpenCode 1 databases that have no credential table.
|
Both review findings addressed in the latest commit:
Docs updated ( |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2dd2b63e47
ℹ️ 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".
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the behavioral changes in 2dd2b63 since the previous review at d6b7dff.
- Separated channel attribution: Matched each database's usage to its own current credential and OAuth creation time, removing global credential ranking.
- Restricted legacy fallback: Treated an empty OpenCode 2 credential table as logged out, preserved file fallback for missing legacy tables, and routed credential failures through the scanner's error reporting.
- Added regressions and documentation: Covered mixed channel credentials, separate login cutoffs, and stale-file logout behavior, and documented channel-specific attribution.
Validated the production auth store and scanner with an isolated Swift 6 harness executing real SQLite fixtures, including exact token totals for separate channel cutoffs and successful sibling scans after a database failure. The full macOS suite was not run locally on this Linux runner; GitHub's Build and Test check passed.
GPT Astra | 𝕏
…nversion A time_created outside Int's range would trap in the scanner's Int(Double) conversion. Parse it at the boundary and surface a malformed row as credentialsUnreadable, which the scanner logs as a per-database read failure.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the timestamp-validation fix in 8dbbd2c since the previous review at 2dd2b63.
- Bounded credential timestamps: Rejected invalid or out-of-range creation times before the scanner converts them to integer milliseconds.
- Surfaced malformed credentials: Changed malformed database rows into read failures without reviving the legacy file fallback.
- Added regression coverage: Asserted the expected error for negative and overflowing timestamp values.
Validated the production auth store and numeric parser with an isolated Swift 6 harness covering invalid timestamps, exact integer conversions at valid boundaries, null timestamps, and malformed rows. All checks passed. The full macOS suite could not run on this Linux runner; GitHub's Build and Test check was still running when inspected.
GPT Astra | 𝕏
|
Run failed. View the logs →
|
|
@pullfrog review |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed all nine changed files covering OpenCode 2 usage attribution to Codex, regression tests, and provider documentation.
- Credential attribution: Reads each channel database’s current OpenAI credential, limits
auth.jsonfallback to legacy databases, and rejects implausible timestamps. - Usage scanning: Supports both message schemas, counts completed assistants and compactions, and applies each channel’s OAuth creation cutoff only to v2 rows.
- Tests and documentation: Covers channel isolation, logout, read failures, schema selection, and timestamp validation, with updated attribution guidance.
Validated the production SQL templates against real in-memory SQLite fixtures for v1-only, v2-only, and mixed databases, including completion, cost, and login-time filters and credential selection. Checked the v2 record shapes against OpenCode’s beta message schema. The full macOS suite was not run on this Linux runner; GitHub’s Build and Test check passed.
GPT Astra | 𝕏
Addresses the pullfrog review on robinebers#1296: - Cowork discovery was narrowed to the card's own account before the shared bypass ran, so each card reported a different partial total. Shared scans now discover every account's Cowork sessions. - Shared cards asked pi for their own card ID, which pi never matches for an account card such as claude@1234abcd. They now ask for the claude family, the same way Codex asks for codex. - Both caret sections can carry a Shared pill at once, and the two rows shared one identity. Each section's pill now has its own. Also merges current main (robinebers#1284 and the changelog); the merged state builds and passes the full suite.

TL;DR
Codex Today stayed empty after upgrading to OpenCode 2 because usage moved to
session_messageand OAuth moved to thecredentialtable. This reads both, picks the live OpenAI credential across channel databases, and simplifies the OpenCode scanning code.What was happening
session_messageand OAuth to the SQLitecredentialtable; the v1messagetable andauth.jsonstopped receiving new rows, so the Codex card showed nothing.opencode-next.dbsorted beforeopencode.dband could win the OAuth check.auth.jsoncould revive when a credential query failed.active/time_updatedwere converted withInt(Double), which traps on out-of-range values.What this changes
session_message(v2) alongsidemessage(v1); the OAuth-creation cutoff bounds only v2 rows so pre-upgrade history still counts.credentialrows globally across databases and does not fall back toauth.jsonon a hard SQLite failure.Double.sourcecolumn or Swift post-filter), schema probe moved into the scanner,OpenCodePathsis paths-only, tests share one store factory and match exact SQL. Net -141 lines.docs/providers/codex.md,docs/providers/opencode.md.Heads-up
auth.jsonfallback for that refresh (self-corrects next refresh); deliberate so staleauth.jsoncannot revive.UsageLogReadFailureReporteris a follow-up.OpenCodeUsageScanner(the OpenCode card) still reads onlymessage, so v2 installs misssession_messagerows there.Tests
swift build --build-tests; OpenCode/Codex suites: 201 tests, 0 failures. Generated SQL verified against a real sqlite3 database.