Repository navigation
fix(code init): delegate tool login to /connect and /login — stop writing tool auth stores - #91
Conversation
…ing tool auth stores
The CLI used to copy its own OAuth tokens (or a created API key) into the
tool's private auth file (~/.local/share/opencode/auth.json, Pi auth.json)
and report "You're all set!". OpenCode V2 moved to a credential database
and silently never imports that file on fresh installs, so new users ended
up unconnected — falling back to BERGET_API_KEY and draining the org API
pot while their seat sat at 0%.
Authentication is now always performed by the tool itself, which is the
only version-proof contract and gives each tool its own Keycloak session
(no shared refresh tokens):
- Subscription path: instruct /connect (OpenCode) or /login (Pi), write
nothing
- API key path: create the key, display it once with paste/env
instructions, write nothing
- Reconfigure: remove the stale berget entry so the tool falls back to
its own login flow instead of retrying a dead credential
- Existing working auth is untouched ('keep' still short-circuits)
Removes syncOAuthToTool/syncApiKeyToTool; adds removeToolAuth.
| // Fall through to reconfigure | ||
| // Reconfigure: drop the stale entry so the tool falls back cleanly to its | ||
| // own login flow instead of retrying a potentially dead credential. | ||
| await removeToolAuth(files, homeDir, tool); |
There was a problem hiding this comment.
🟠 warning — Reconfigure deletes the existing credential before the replacement flow completes — any later failure/cancellation leaves the user with no working auth; delete after success (or not at all, since /connect overwrites the entry).
| * tool is the only version-proof contract, and gives each tool its own | ||
| * Keycloak session (no shared refresh tokens). | ||
| */ | ||
| const SUBSCRIPTION_LOGIN_INSTRUCTIONS = { |
There was a problem hiding this comment.
🟡 nit — These login instructions duplicate the same /connect, /login and BERGET_API_KEY guidance in init.ts buildNextSteps; both notes are shown in one init run, so they can drift — share one source.
| type: 'oauth', | ||
| }, | ||
| }; | ||
| if (!('berget' in existing)) return; |
There was a problem hiding this comment.
🟡 nit — 'berget' in existing throws TypeError on valid non-object JSON (e.g. "null"); guard with typeof existing !== 'object' || existing === null to honor the 'does nothing' contract.
| * private auth store from the CLI. OpenCode V2 moved its credential store and | ||
| * silently ignores the V1 auth.json we used to write; delegating login to the | ||
| * tool is the only version-proof contract, and gives each tool its own | ||
| * Keycloak session (no shared refresh tokens). |
There was a problem hiding this comment.
✅ good — Dropping the CLI-authored refresh token from the tool's auth store is a real security improvement — each tool now holds its own Keycloak session.
ReviewSummaryThis PR reverses the RiskMEDIUM — the direction is sound and the security posture improves (no more shared refresh tokens), but a mid-flow failure on the reconfigure path now leaves the user with less working auth than before, and the detect/keep/cleanup logic still targets the very auth.json that the PR's own comment says OpenCode V2 no longer consults. Issues
Suggestions
ArchitectureThis inverts the trust boundary between CLI and tool: the tool's auth store becomes tool-owned (CLI only deletes legacy entries), making auth version-proof per the Code quality: 8/10 — clean, well-commented state-machine with dead paths removed and comprehensive test updates, held back by duplicated instruction strings and one over-narrow defensive guard. Inline findingsBerget AI (berget/zai-org/GLM-5.3-Flash) | PR #91 |
… instructions - Drop removeToolAuth: deleting the old berget entry before the replacement was established could leave a cancelled/failed reconfigure with LESS working auth. The tool's own login overwrites the entry on success, so the delete served no purpose on the success path either. - Export SUBSCRIPTION/API_KEY_LOGIN_INSTRUCTIONS and reuse them in init.ts#buildNextSteps — one canonical source for /connect //login guidance, no drift between the inline notes and the final panel. - Document that isToolAuthenticated is deliberately V1-file-only: reading OpenCode V2's SQLite credential store would repeat the private-store mistake this PR removes.
| return tool === 'opencode' | ||
| ? 'Next steps:\n\n1. Run: opencode\n2. Type: /connect\n3. Choose your auth method:\n • "Login with Berget" — Berget Code plan\n • "Enter Berget API Key manually"\n • (or set BERGET_API_KEY env var)\n4. Select model: /models' | ||
| : 'Next steps:\n\n1. Restart Pi or run /reload\n2. Type: /login\n3. Choose your auth method:\n • "Use a subscription" → Berget AI\n • (or set BERGET_API_KEY env var)\n4. Select model: /model'; | ||
| ? `Next steps:\n\n1. ${SUBSCRIPTION_LOGIN_INSTRUCTIONS.opencode}\n ${API_KEY_LOGIN_INSTRUCTIONS.opencode}\n2. Select model: /models` |
There was a problem hiding this comment.
🟡 nit — API_KEY_LOGIN_INSTRUCTIONS.* contains an embedded \n, so its second line ('or set the BERGET_API_KEY environment variable.') renders flush-left instead of indented in the next-steps note (same for pi at line 171).
Problem
berget code initcopied the CLI's own OAuth tokens (or a freshly created API key) into the tool's private auth file (~/.local/share/opencode/auth.json, Pi'sauth.json) and finished with "You're all set!".OpenCode V2 moved provider credentials to a SQLite credential store and — verified against
@opencode/[email protected]— never runs its legacy auth.json import on fresh installs. Result (real customer report): a new user ran init, picked their seat, and OpenCode was not connected. WithBERGET_API_KEYset, requests silently fell back to the organization's API pot while the seat sat at 0%. Only/connect(which writes via V2's own credential API) connected the seat.Writing another app's private data store was always fragile; V2 proved it. Detecting the OpenCode version and writing the V2 SQLite schema instead would be the same mistake again (drizzle schema, migration journal, WAL locking against a running server, per-version drift).
Change — delegate authentication to the tool, for both tools consistently
initconfigures; the tool authenticates. Same model for OpenCode and Pi:opencode, type/connect→ Login with Berget" (Pi:/login→ Use a subscription → Berget AI). Writes nothing.BERGET_API_KEYinstructions. Writes nothing.bergetentry so the tool falls back to its own login flow instead of retrying a dead credentialSide benefits:
invalid_grant) disappears without any sync logic.syncOAuthToTool/syncApiKeyToToolremoved (−237/+179 lines incl. tests).The final next-steps panel already contained the correct
/connect///logininstructions for the not-authenticated case, so those paths now surface automatically.Tests
auth-sync.test.ts/init.test.ts: every path asserts nothing is written to the tool auth store; newremoveToolAuthsuite (removal, preservation of other providers, missing/malformed file).npm run test:run— 271 passed (24 files)npm run typecheck,npm run lint,npm run format— cleanFollow-ups (separate PRs)
/connectstep to the "Invite your team" text (stops the onboarding bleed for new seat users)opencode-berget-auth+pi-providerRefs: customer report (Dan Bergh Johnsson), root-cause research in
opencode-berget-authPR #45 /src/v2/migrate.ts(broken fresh-db V1 import in@opencode/[email protected]).