Skip to content

fix(codex): attribute OpenCode 2 ChatGPT OAuth usage - #1284

Merged
robinebers merged 7 commits into
mainfrom
fix/codex-opencode-v2-support
Sep 23, 2026
Merged

robinebers merged 7 commits into
mainfrom
fix/codex-opencode-v2-support

Conversation

@validatedev

Copy link
Copy Markdown
Collaborator

TL;DR

Codex Today stayed empty after upgrading to OpenCode 2 because usage moved to session_message and OAuth moved to the credential table. This reads both, picks the live OpenAI credential across channel databases, and simplifies the OpenCode scanning code.

What was happening

  • OpenCode 2 writes sessions to session_message and OAuth to the SQLite credential table; the v1 message table and auth.json stopped receiving new rows, so the Codex card showed nothing.
  • A leftover opencode-next.db sorted before opencode.db and could win the OAuth check.
  • A stale imported auth.json could revive when a credential query failed.
  • active/time_updated were converted with Int(Double), which traps on out-of-range values.

What this changes

  • Reads session_message (v2) alongside message (v1); the OAuth-creation cutoff bounds only v2 rows so pre-upgrade history still counts.
  • Ranks current credential rows globally across databases and does not fall back to auth.json on a hard SQLite failure.
  • Keeps credential ranking fields as Double.
  • Refactor: OAuth bound lives in the v2 SQL branch (no source column or Swift post-filter), schema probe moved into the scanner, OpenCodePaths is paths-only, tests share one store factory and match exact SQL. Net -141 lines.
  • Docs: docs/providers/codex.md, docs/providers/opencode.md.

Heads-up

  • A transient SQLite lock disables the auth.json fallback for that refresh (self-corrects next refresh); deliberate so stale auth.json cannot revive.
  • Credential-lookup failures warn on every refresh rather than once; the auth store is a sync struct so wiring UsageLogReadFailureReporter is a follow-up.
  • Out of scope: OpenCodeUsageScanner (the OpenCode card) still reads only message, so v2 installs miss session_message rows there.

Tests

swift build --build-tests; OpenCode/Codex suites: 201 tests, 0 failures. Generated SQL verified against a real sqlite3 database.

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.
@robinebers

Copy link
Copy Markdown
Owner

@pullfrog review this

@pullfrog pullfrog 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.

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.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using GPT Astra𝕏

Comment thread Sources/OpenUsage/Providers/OpenCode/OpenCodeAuthStore.swift
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.
@validatedev

Copy link
Copy Markdown
Collaborator Author

Both review findings addressed in the latest commit:

  • Empty credential tableopenAICredential(databasePath:) now distinguishes a successful empty result (OpenCode 2 logout → not OAuth, no auth.json fallback) from a missing table (OpenCode 1 → auth.json still authoritative) and a hard failure (throws; the scanner records it as a per-database read failure). Regression: testEmptyCredentialTableDoesNotFallBackToStaleAuthFile, testLoggedOutV2DatabaseIsSkippedWhileAuthFileStillHoldsOAuth.
  • Per-channel credentials — the scanner evaluates each database with its own credential and login time; the global cross-database ranking is gone. Regressions assert totals and per-database SQL bounds: testEachChannelDatabaseIsGatedByItsOwnCredential, testEachOAuthChannelUsesItsOwnLoginTimeAsTheV2Bound, testEachChannelDatabaseIsJudgedByItsOwnCredential.

Docs updated (docs/providers/codex.md, docs/providers/opencode.md). Credential SQL verified against real sqlite3 for the empty-table, row, and no-table cases.

@validatedev

Copy link
Copy Markdown
Collaborator 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: 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".

@pullfrog pullfrog 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.

✅ 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.

Pullfrog  | View workflow run | Using 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.

@pullfrog pullfrog 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.

✅ 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.

Pullfrog  | View workflow run | Using GPT Astra𝕏

@pullfrog

pullfrog Bot commented Sep 20, 2026

Copy link
Copy Markdown

Run failed. View the logs →

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using GPT Astra𝕏

@robinebers

Copy link
Copy Markdown
Owner

@pullfrog review

@pullfrog pullfrog 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.

✅ 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.json fallback 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.

Pullfrog  | View workflow run | Using GPT Astra𝕏

@robinebers
robinebers merged commit 333401f into main Sep 23, 2026
4 checks passed
@robinebers
robinebers deleted the fix/codex-opencode-v2-support branch September 23, 2026 05:08
maddada added a commit to maddada/openusage that referenced this pull request Sep 23, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants