Skip to content

fix: support OpenCode 2.x credential storage - #160

Open
ykai55 wants to merge 3 commits into
opgginc:mainfrom
ykai55:fix/opencode-v2-credentials
Open

ykai55 wants to merge 3 commits into
opgginc:mainfrom
ykai55:fix/opencode-v2-credentials

Conversation

@ykai55

@ykai55 ykai55 commented Oct 20, 2026

Copy link
Copy Markdown

Summary

OpenCode 2.x maintains credentials in the SQLite database after importing legacy auth.json. Reading the old file can therefore produce expired-token errors even while OpenCode itself works.

  • Read the credential table with SQLITE_OPEN_READONLY, selecting the active credential per integration or the most recently updated entry.
  • Map OAuth account metadata and API keys into the existing provider auth model, including the separate Gemini OAuth reader.
  • Honor XDG_DATA_HOME and absolute or relative OPENCODE_DB paths.
  • Keep OpenCode 1.x discovery when the database is absent. Existing empty, invalid, or unreadable databases do not fall back to stale JSON credentials.
  • Show the actual credential source in provider menus and diagnostics, and document discovery behavior.

Validation

  • Xcode Debug build and 37 selected tests passed (TokenManagerTests and OpenCodeAuthDecodingTests).
  • Added regression coverage for V1 fallback, V2 precedence, active/newest credential selection, OAuth metadata, API keys, Gemini, malformed entries, empty databases, schema failures, and path overrides.
  • Launched the locally built menu bar app; runtime logs confirmed credentials loaded from opencode.db.
  • The built CLI successfully fetched live ChatGPT usage.
  • git diff --check passed. SwiftLint was unavailable locally.

The database is read-only; credential refresh remains owned by OpenCode.

Read active integration credentials from the OpenCode SQLite database in read-only mode, including OAuth metadata and API keys. Respect XDG_DATA_HOME and OPENCODE_DB, and use legacy auth.json discovery only when the database is absent.

Share credential loading with Gemini OAuth and show the actual credential source in provider menus and diagnostics.
@opgg-ai-devops

opgg-ai-devops Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Initial model: zzgg/claude-opus-5-5

Model usage by lane
Lane / task Model Thinking Speed Elapsed Tok/s Input Output Cache read Cache write Total
Main ✓ claude-opus-5-5 medium N/A 13m 27s 79.7 28 19,909 1,183,579 134,033 1,337,549
DependencyContext ✓ claude-sonnet-5-5 medium N/A 49s 97.8 14 4,790 151,599 40,685 197,088
lane-a-slop ✓ claude-sonnet-5-5 medium N/A 48s 87.8 12 4,179 97,771 28,139 130,101
lane-b-dry ✓ claude-sonnet-5-5 medium N/A 1m 3s 60.9 14 3,801 139,027 31,844 174,686
lane-c-reviewer ✓ claude-opus-5-5 medium N/A 9m 11s 58.8 80 32,340 7,160,887 316,018 7,509,325
lane-d-ponytail ✓ claude-sonnet-5-5 medium N/A 49s 72.5 12 3,537 100,053 28,307 131,909
RepoContext ✓ claude-sonnet-5-5 medium N/A 51s 91.2 14 4,668 163,510 41,109 209,301
ResolvedIssues ✓ claude-sonnet-5-5 medium N/A 29s 65.4 14 1,901 68,562 11,224 81,701
Total 13m 27s 188 75,125 9,064,988 631,359 9,771,660
gantt
    dateFormat x
    axisFormat %H:%M
    todayMarker off
    section Main
    claude-opus-5-5 : 1792478346537, 1792479153476
    section DependencyContext
    claude-sonnet-5-5 : 1792478436048, 1792478485231
    section lane-a-slop
    claude-sonnet-5-5 : 1792478436057, 1792478483720
    section lane-b-dry
    claude-sonnet-5-5 : 1792478436501, 1792478499082
    section lane-c-reviewer
    claude-opus-5-5 : 1792478435673, 1792478987079
    section lane-d-ponytail
    claude-sonnet-5-5 : 1792478436738, 1792478485854
    section RepoContext
    claude-sonnet-5-5 : 1792478435649, 1792478487130
    section ResolvedIssues
    claude-sonnet-5-5 : 1792478436516, 1792478465742
Loading

Input excludes cached tokens. Output includes reasoning tokens. Speed reflects the requested API tier; provider acceptance is not recorded.
Elapsed spans the first request start to the last response end. Tok/s is output tokens per second of model response time, excluding tool execution and failed requests. ✗ marks a model with a failed request; the reason is listed under the table. ✓ marks a finished lane and ⏳ a lane still running. The timeline is drawn in your local time.

@opgg-ai-devops opgg-ai-devops Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary

This is a real fix with a real payoff: OpenCode 2.x users stop getting expired-token errors from a stale auth.json while OpenCode itself works fine. The core design is solid. Nothing blocking, so approving. There's one test you broke that you should fix before merging, plus some cleanup.

What's good

  • getOpenCodeAuthPaths() is now the single gate in front of getAuthFilePaths(). readOpenCodeAuth, readGeminiOAuthAuth and both debug dumps go through it, and I couldn't find any Swift path that sneaks around it. So "an existing DB is authoritative, no silent fallback to stale JSON" actually holds.
  • The DB is opened SQLITE_OPEN_READONLY, finalize/close are both in defer, and you check SQLITE_DONE after the loop, so a partial read errors out instead of quietly returning half the credentials. That matches the repo's "usage collection only reads credentials, never refreshes them" rule.
  • Row selection is deterministic (active DESC, time_updated DESC, first row wins per integration). A malformed active row doesn't bring a stale credential back, and the test covers that (anthropic → nil).
  • Mapping rows back into the legacy shape so the existing lossy OpenCodeAuth decoder keeps working is the smallest way to support both formats. A second schema would have cost more.
  • The test runs against real SQLite instead of mocks and covers V1 fallback, V2 precedence, active vs newest, metadata, key→api, Gemini, empty table and dropped table. Solid.

What to fix

  • major: MiniMaxProviderTests.swift:123 still asserts the old ~/.local/share/opencode/auth.json literal. It fails on any machine that has a MiniMax key. See inline.
  • minor: accountId/enterpriseUrl get clobbered when metadata lacks the key. Three different authSource fallback conventions now exist, and nil drops the "Token From" row. Diagnostics mark an empty DB as ACTIVE. The DB-vs-JSON check is re-derived in three places, and the two debug search loops are copy-pasted. Data-dir resolution is duplicated. The README still has stale auth.json-only sections.

Out of diff (FYI, not blocking)

  • scripts/query-{codex,copilot,kimi,minimax,nano-gpt,claude}.sh and docs/AI_USAGE_API_REFERENCE.md still read or document only ~/.local/share/opencode/auth.json. OpenCode 2.x users of those scripts will hit the same stale-token problem this PR fixes. Worth a follow-up.
  • Doc comments outside the hunks still say "OpenCode auth.json" for data that can now come from the DB: GeminiAuthSource.opencodeAuth (TokenManager.swift:603), the cachedGeminiOAuthAuth/lastFoundGeminiOAuthPath comments (:867, :871), and // 1. Check all possible auth.json paths in both debug dumps.
  • The existing codex-lb reader (queryCodexLBEncryptedAccounts) copies the DB plus -wal/-shm to a temp dir before a read-only open. This PR opens OpenCode's live DB in place. The repo doesn't tell us whether a read-only open of a WAL DB works when OpenCode isn't running and there's no -shm file, and your runtime check was presumably with OpenCode running. Do a quick manual run with OpenCode quit before you ship.
  • testGetOpenAIAccountsIncludesOpenCodeAPIKeyAccount doesn't unset OPENCODE_DB. If a runner's environment points that at a real DB, the DB takes over and the test gets flaky.

Validation signal

  • Smoke tests: skipped (fork-source-unavailable, recorded as OVERALL status=skipped), so this review has no independent build/test signal.
  • CI at review time: Lint passed. Test, CI and Build and Release were still running, so their results are unknown.
  • No dependency or manifest changes. The new code uses the system SQLite3 module, which is already imported elsewhere. No known-critical advisory matched in existing manifests.
  • No env-surface concerns: XDG_DATA_HOME and OPENCODE_DB are OpenCode's own variables being honored, not new app config knobs.

Comment thread CopilotMonitor/CopilotMonitor/Providers/MiniMaxProvider.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Providers/OpenRouterProvider.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread README.md Outdated
Use a consistent source label across providers and recognize custom credential database paths. Carry the selected storage format into readers, share credential diagnostics, and distinguish empty stores from usable credentials.

Preserve account metadata when optional database metadata is absent. Isolate credential fixtures from local accounts, cover WAL reads after the writer closes, and update troubleshooting guidance for OpenCode 2.x.
@ykai55

ykai55 commented Oct 20, 2026

Copy link
Copy Markdown
Author

Addressed the actionable review findings in 26309a8:

  • Fixed the MiniMax source assertion and made its success test use isolated temporary credentials, so it runs without a locally configured MiniMax account.
  • Unified OpenCode source labels across providers and recognized custom database paths in menu source classification.
  • Passed the selected DB/JSON format into readers instead of re-deriving it from the environment. Database filenames do not need a .db extension.
  • Consolidated both diagnostic search loops. Empty or unsupported stores now report NO USABLE CREDENTIALS; read/schema failures report UNREADABLE rather than ACTIVE.
  • Preserved top-level account fields when optional metadata fields are absent.
  • Isolated OPENCODE_DB in the existing legacy-auth test and updated README troubleshooting and stale source comments.

Validation: the full Debug suite completed with 314 tests, 15 skipped, and 0 failures. The MiniMax success case ran. WAL coverage exercises both a live writer and a closed/checkpointed writer with its shared-memory sidecar removed. This uses a disposable SQLite fixture rather than stopping the user's OpenCode service. The rebuilt menu app was relaunched and logged successful database loading; the rebuilt CLI fetched live ChatGPT usage. SwiftLint is still unavailable locally.

I kept the existing V1 path discovery to preserve its macOS fallback behavior. OpenRouter currently uses TokenManager.shared throughout and has no injected token manager; this change does not introduce a separate injection refactor. The standalone query scripts and API reference remain follow-up work outside this PR.

@opgg-ai-devops

opgg-ai-devops Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Initial model: zzgg/claude-opus-5-5

Model usage by lane
Lane / task Model Thinking Speed Elapsed Tok/s Input Output Cache read Cache write Total
Main ✓ claude-opus-5-5 medium N/A 15m 55s 78.2 58 33,934 3,067,472 105,334 3,206,798
ContextAudit ✓ claude-sonnet-5-5 medium N/A 2m 18s 60.6 10 2,064 51,310 10,857 64,241
ContextAudit.lane-e-ponytail-audit ✓ claude-sonnet-5-5 medium N/A 1m 0s 52.0 20 3,112 275,552 52,351 331,035
ContextAudit.ResolvedIssues ✓ claude-sonnet-5-5 medium N/A 39s 48.7 16 1,901 88,163 13,866 103,946
DependencyContext ✓ claude-sonnet-5-5 medium N/A 48s 81.8 14 3,892 121,415 26,018 151,339
lane-a-slop ✓ claude-sonnet-5-5 medium N/A 1m 9s 78.8 16 5,417 202,874 38,412 246,719
lane-b-dry ✓ claude-sonnet-5-5 medium N/A 1m 6s 47.2 14 3,077 143,730 30,587 177,408
lane-c-reviewer ✓ claude-opus-5-5 medium N/A 8m 33s 56.9 74 29,103 5,880,748 333,641 6,243,566
lane-d-ponytail ✓ claude-sonnet-5-5 medium N/A 51s 69.4 12 3,501 119,792 32,858 156,163
RepoContext ✓ claude-sonnet-5-5 medium N/A 1m 43s 54.9 16 5,644 200,591 48,673 254,924
Total 15m 55s 250 91,645 10,151,647 692,597 10,936,139
gantt
    dateFormat x
    axisFormat %H:%M
    todayMarker off
    section Main
    claude-opus-5-5 : 1792479888424, 1792480842965
    section ContextAudit
    claude-sonnet-5-5 : 1792479972809, 1792480110724
    section ContextAudit.lane-e-ponytail-audit
    claude-sonnet-5-5 : 1792480040854, 1792480101059
    section ContextAudit.ResolvedIssues
    claude-sonnet-5-5 : 1792479981040, 1792480020294
    section DependencyContext
    claude-sonnet-5-5 : 1792479973530, 1792480021346
    section lane-a-slop
    claude-sonnet-5-5 : 1792479974251, 1792480043355
    section lane-b-dry
    claude-sonnet-5-5 : 1792479973115, 1792480038627
    section lane-c-reviewer
    claude-opus-5-5 : 1792479974229, 1792480487240
    section lane-d-ponytail
    claude-sonnet-5-5 : 1792479973542, 1792480024189
    section RepoContext
    claude-sonnet-5-5 : 1792479974026, 1792480077108
Loading

Input excludes cached tokens. Output includes reasoning tokens. Speed reflects the requested API tier; provider acceptance is not recorded.
Elapsed spans the first request start to the last response end. Tok/s is output tokens per second of model response time, excluding tool execution and failed requests. ✗ marks a model with a failed request; the reason is listed under the table. ✓ marks a finished lane and ⏳ a lane still running. The timeline is drawn in your local time.

@opgg-ai-devops opgg-ai-devops Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary

Round 2, and honestly this one cleaned up nicely. The fix commit (26309a8) resolves most of what I raised last time:

  • Top-level accountId now survives when metadata is empty, and a test covers it.
  • Every provider uses one openCodeAuthSourceLabel, so the three competing authSource conventions are gone. OpenRouter really has no injected tokenManager, so TokenManager.shared there is fine.
  • An absolute OPENCODE_DB is now recognized as an OpenCode source in the menu.
  • Readers take the DB/JSON decision from getOpenCodeAuthStores() instead of re-reading env in three places.
  • Diagnostics no longer mark an empty DB as ACTIVE. Empty databases show NO USABLE CREDENTIALS and dropped tables show UNREADABLE, and tests assert both.
  • MiniMaxProviderTests is hermetic now and doesn't depend on whatever MiniMax key the developer has locally. Good.
  • README troubleshooting and "How It Works" now describe the DB-first rule.

The SQLite reader itself is solid. It opens with SQLITE_OPEN_READONLY, uses defer for close/finalize, and selects credentials deterministically with active DESC, time_updated DESC. That fits the repo rule that usage collection only reads existing credentials and never refreshes them. The WAL-without--shm test case is a nice touch tbh.

What's left is all minor, so I'm approving. The comments are cleanups worth doing before merge:

  • A database file with no credential table blocks the auth.json fallback. Probably cheap insurance to fix.
  • One dead clause in parseSingleSource makes a main-thread queue.sync call on every menu rebuild.
  • Diagnostics re-implement the readers' checks and can name the wrong file in the legacy multi-file case.
  • The enterpriseUrl mapping is dead.
  • Dropped rows are silent, and SQLite errors are logged as bare codes.
  • Two README lines still say auth.json.

Not inline (outside the diff or non-blocking)

  • scripts/query-*.sh and docs/AI_USAGE_API_REFERENCE.md still read ~/.local/share/opencode/auth.json directly. On 2.x they'll keep returning the stale tokens this PR fixes in the app. You called this out as follow-up, which is fair. Just don't let it rot, since AGENTS.md points devs at those scripts for quota checks.
  • testOpenCodeDatabasePrecedenceAndCredentialSelection packs about 10 scenarios into one function, and the XDG_DATA_HOME/OPENCODE_DB save/restore fixture is copy-pasted across TokenManagerTests and MiniMaxProviderTests. A small withOpenCodeDataHome helper plus a few focused tests would make failures easier to read. Not blocking.
  • The XDG_DATA_HOME SET / NOT SET block is still duplicated across the two debug-report builders, and neither prints OPENCODE_DB or the resolved DB path. That path is what decides the store now, so it's worth logging.
  • Last round's data-dir duplication point: your reason (the DB has one default location, while legacy JSON keeps the macOS Application Support fallback) holds up, and the "absolute path shows as Unknown" symptom is fixed. Dropping it.

Validation signal

  • Smoke tests: skipped. This is a fork PR and the fork source wasn't available to the isolated smoke job (OVERALL status=skipped), so I have no independent build or test signal.
  • CI on 26309a8: Lint passed. Test, CI, and Build and Release were still running when the context was fetched.
  • The author reports 314 tests passing locally (15 skipped), plus a manual app and CLI run against a live opencode.db. SwiftLint wasn't available locally, but the Lint workflow passed.

Additional findings

3 findings — expand
  • delete: The OpenCode source check in the status bar's source parser has a redundant label-equality clause. Every value that clause can match already contains "opencode" (the database path, the auth.json path, and the fallback label), so the earlier substring test returns first. Only the database-path comparison for a custom absolute OPENCODE_DB adds anything. [CopilotMonitor/CopilotMonitor/App/StatusBarController.swift]
  • yagni: getOpenCodeDatabasePath re-implements XDG_DATA_HOME and home-directory resolution that buildOpenCodeFilePaths already provides for auth.json. It is also widened to non-private only so the status bar can compare source strings. Two copies of the data-directory rule must now change together. [CopilotMonitor/CopilotMonitor/Services/TokenManager.swift, CopilotMonitor/CopilotMonitor/App/StatusBarController.swift]
  • shrink: The two near-identical auth debug-dump routines in TokenManager still duplicate the XDG_DATA_HOME report block and the credential-store search section. The PR had to edit both copies identically, so one shared routine would be smaller and could not drift. [CopilotMonitor/CopilotMonitor/Services/TokenManager.swift]

Unresolved last round (not re-raised this round)

  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — Metadata mapping clobbers top-level accountId: account ID can silently vanish (round 1)
  • CopilotMonitor/CopilotMonitor/Providers/OpenRouterProvider.swift — Three authSource conventions now: Token From row can disappear (round 1)
  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — Data-dir resolution duplicated and hand-rolled: absolute OPENCODE_DB shows as Unknown (round 1)
  • CopilotMonitor/CopilotMonitor/Providers/MiniMaxProvider.swift — MiniMax test still asserts old literal: fails on any machine with a MiniMax key (round 1)
  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — DB-vs-JSON decided by re-reading env in 3 places: reader can drift from path picker (round 1)
  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — Diagnostics mark an empty DB ACTIVE: troubleshooting output contradicts reality (round 1)

Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift Outdated
Comment thread CopilotMonitor/CopilotMonitor/App/StatusBarController.swift Outdated
Comment thread README.md
Probe for the credential table before choosing the SQLite auth store. Preserve legacy auth.json discovery for V1 databases that only contain session data while keeping database errors and invalid credential schemas authoritative.

Report the sources selected by each auth reader, include actionable SQLite errors and rejected-row diagnostics, remove unused metadata mapping and redundant menu synchronization, and clarify V1/V2 discovery in the README.
@ykai55

ykai55 commented Oct 20, 2026

Copy link
Copy Markdown
Author

Addressed round 2 in 548e815.

The V1 database compatibility concern is confirmed against OpenCode v1.2.0 source:

Store discovery now probes sqlite_master read-only. A successful probe finding no credential table enables legacy JSON discovery. An existing credential table, or a failed probe, keeps SQLite authoritative. Regression coverage now includes a V1 session-only database, an empty credential table, an invalid credential schema, and a corrupt database file.

Other changes:

  • Diagnostics use the actual auth readers and separately report the selected general-provider and Gemini OAuth paths; they no longer duplicate credential acceptance rules. Both reports share environment/DB-path diagnostics.
  • Removed the redundant main-thread source-label synchronization and unused enterpriseUrl mapping.
  • Added rejection reasons for invalid/unsupported credential rows and SQLite error descriptions, without logging credential payloads.
  • Updated the remaining README source references and documented the schema-based V1 fallback.

Validation: full Debug suite, 314 tests, 15 skipped, 0 failures. Relaunched the rebuilt menu app and confirmed database loading in its logs. The rebuilt CLI fetched live ChatGPT usage. SwiftLint remains unavailable locally.

@opgg-ai-devops

opgg-ai-devops Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

✅ AI Code Review Completed

Review finished. Check the PR for inline comments.


📋 View Logs | 🤖 Initial model: zzgg/claude-opus-5-5

Model usage by lane
Lane / task Model Thinking Speed Elapsed Tok/s Input Output Cache read Cache write Total
Main ✓ claude-opus-5-5 medium N/A 7m 41s 74.4 32 20,883 1,431,387 123,265 1,575,567
ContextAudit ✓ claude-sonnet-5-5 medium N/A 2m 30s 69.0 10 2,020 52,437 11,402 65,869
ContextAudit.lane-e-ponytail-audit ✓ claude-sonnet-5-5 medium N/A 59s 59.5 20 3,465 297,425 53,723 354,633
ContextAudit.ResolvedIssues ✓ claude-sonnet-5-5 medium N/A 1m 0s 54.7 16 3,264 112,393 22,609 138,282
DependencyContext ✓ claude-sonnet-5-5 medium N/A 40s 87.4 14 3,501 79,845 17,958 101,318
lane-a-slop ✓ claude-sonnet-5-5 medium N/A 1m 2s 78.2 14 4,795 163,146 45,329 213,284
lane-b-dry ✓ claude-sonnet-5-5 medium N/A 51s 63.4 14 3,229 148,870 36,165 188,278
lane-c-reviewer ✓ claude-opus-5-5 medium N/A 2m 54s 61.5 36 10,651 1,231,200 105,305 1,347,192
lane-d-ponytail ✓ claude-sonnet-5-5 medium N/A 57s 60.3 14 3,453 148,413 35,309 187,189
RepoContext ✓ claude-sonnet-5-5 medium N/A 1m 3s 99.1 18 6,165 221,309 43,838 271,330
Total 7m 41s 188 61,426 3,886,425 494,903 4,442,942
gantt
    dateFormat x
    axisFormat %H:%M
    todayMarker off
    section Main
    claude-opus-5-5 : 1792481394429, 1792481855649
    section ContextAudit
    claude-sonnet-5-5 : 1792481447764, 1792481597891
    section ContextAudit.lane-e-ponytail-audit
    claude-sonnet-5-5 : 1792481530198, 1792481588777
    section ContextAudit.ResolvedIssues
    claude-sonnet-5-5 : 1792481456100, 1792481515914
    section DependencyContext
    claude-sonnet-5-5 : 1792481448488, 1792481488748
    section lane-a-slop
    claude-sonnet-5-5 : 1792481448608, 1792481510134
    section lane-b-dry
    claude-sonnet-5-5 : 1792481447901, 1792481499340
    section lane-c-reviewer
    claude-opus-5-5 : 1792481448595, 1792481622670
    section lane-d-ponytail
    claude-sonnet-5-5 : 1792481448574, 1792481506069
    section RepoContext
    claude-sonnet-5-5 : 1792481448587, 1792481511413
Loading

Input excludes cached tokens. Output includes reasoning tokens. Speed reflects the requested API tier; provider acceptance is not recorded.
Elapsed spans the first request start to the last response end. Tok/s is output tokens per second of model response time, excluding tool execution and failed requests. ✗ marks a model with a failed request; the reason is listed under the table. ✓ marks a finished lane and ⏳ a lane still running. The timeline is drawn in your local time.

@opgg-ai-devops opgg-ai-devops Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary

Round 3, and honestly this one landed. Every open item from last round is fixed in 548e815:

  • Store selection. getOpenCodeAuthStores() now probes sqlite_master for a credential table before treating opencode.db as authoritative. A V1 install that only keeps session data in the DB falls back to auth.json again. The probe is read-only, and the test covers it.
  • Diagnostics. [ACTIVE] now comes straight from lastFoundAuthPath / lastFoundGeminiOAuthPath, the paths the readers actually picked. An empty DB shows up as READABLE instead of ACTIVE, and the two copy-pasted 60-line auth.json dump blocks are gone. Nice net deletion.
  • Debuggability. Skipped rows now log the integration_id and the reason. SQLite errors carry sqlite3_errmsg, so "database is locked" and "no such table" no longer look identical.
  • Source labels. All nine providers plus the Claude/Copilot/OpenAI account builders read one openCodeAuthSourceLabel. That replaces the hard-coded ~/.local/share/opencode/auth.json literals, so the "Token From" row can't disappear anymore. The redundant main-thread queue.sync in the menu label parser is gone too.
  • Dead mapping and tests. The dead enterpriseUrl mapping is removed. MiniMax's success test now uses a fixture auth.json instead of the dev's real key.
  • README. "How It Works" and troubleshooting describe the DB-first rule and the OPENCODE_DB / XDG_DATA_HOME overrides. The two stale lines are fixed.

Functional benefit: OpenCode 2.x users get their live credentials from the credential table instead of expired leftovers in auth.json. That fixes the "expired token while OpenCode itself works" bug for every OpenCode-backed provider.

What's left is two minor simplifications in TokenManager.swift. Neither blocks:

  1. readGeminiOAuthAuth and readOpenCodeAuth still run the same store loop. This PR added a second copy of the readability guard to keep them in sync.
  2. readOpenCodeAuthData repeats the NSError builder 3 times and advances the cursor in 2 places.

Both are inline.

Non-blocking notes (no action required)

  • openCodeAuthSourceLabel is read after each provider's network calls, not when the key was read. A concurrent cache refresh elsewhere could in theory change the label in between. It only affects the display, and both readers resolve the same store set, so I'm not pushing on it.
  • The new behavior is covered by one 120-line test, testOpenCodeDatabasePrecedenceAndCredentialSelection. One failure there hides every later assertion. Splitting it with a shared env/temp-dir fixture would also let you drop the duplicate save/restore block in MiniMaxProviderTests. The 401 / sub-1% / row-selection MiniMax tests still XCTSkip without a real key.
  • Two Gemini lines outside the diff (TokenManager.swift:4612, :5084) still fall back to the literal "auth.json", while openCodeAuthSourceLabel falls back to "OpenCode". Worth aligning someday.

Validation

  • Smoke tests: skipped. The fork source wasn't available to the isolated smoke job (fork-source-unavailable), so this review has no local test/typecheck signal.
  • CI at review time: Lint passed. CI, Test, and Build and Release were still running.
  • Dependencies: none added or changed. The only new import is the system SQLite3 module in tests. I found no known-critical issue in the existing manifests.
  • Env vars: OPENCODE_DB and XDG_DATA_HOME are existing OpenCode/XDG conventions that take arbitrary paths, not new single-value knobs. No env-surface concern.

Verdict

APPROVE. No blocking issues remain, and the two inline items are cleanup you can do here or in a follow-up.

Additional findings

2 findings — expand
  • shrink: The new SQLite credential path opens the database several times per lookup (a schema probe in store discovery, a full read, and another full read in the diagnostics listing). It also turns each row into legacy-shaped JSON, serializes it to Data, and decodes it again, so the legacy decoder stays the only model. Rows could map straight to the auth model in one open, with the probe and the read sharing one connection. [CopilotMonitor/CopilotMonitor/Services/TokenManager.swift]
  • shrink: The database-path resolver re-implements the data-home and home-directory resolution that the shared path builder already does for auth.json, with a second non-empty-env check, so two places must change together when the OpenCode data directory rules change. [CopilotMonitor/CopilotMonitor/Services/TokenManager.swift, CopilotMonitor/CopilotMonitor/App/StatusBarController.swift]

Unresolved last round (not re-raised this round)

  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — enterpriseUrl copied into payload nobody decodes: dead mapping (round 1)
  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — Diagnostics re-implement the readers' checks: ACTIVE can name a store no reader picked (round 1)
  • CopilotMonitor/CopilotMonitor/App/StatusBarController.swift — Dead label comparison: extra main-thread queue.sync on every menu rebuild (round 1)
  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — Rows dropped silently and SQLite errors logged as bare codes: undebuggable in the field (round 1)
  • README.md — Two README lines still say auth.json only: 2.x users are pointed at the wrong store (round 2)
  • CopilotMonitor/CopilotMonitor/Services/TokenManager.swift — DB treated as authoritative by file existence alone: an opencode.db without a credential table disables auth.json (round 1)

Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift
Comment thread CopilotMonitor/CopilotMonitor/Services/TokenManager.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant