#4387 · SQLite credential lookup omitted from Go usage collector
Verdict: REPRODUCED · Root-cause confidence: high · reproduction label: confirmed-repro
1. TL;DR
The usage collector can claim that OpenCode Go is unauthenticated when a usable key exists in OpenCode's v2 SQLite credential storage. The collector only considers explicit environment keys, an active Console account, and legacy JSON credentials. A real temporary SQLite database with an active v2 key and empty Console account tables returns unauthenticated without issuing any network request. Giving the same synthetic key to the legacy JSON path makes the test collect all three usage windows. This was repeated from a second clean checkout by the same investigator; no live credentials or real OpenCode account were used.
2. Claims vs findings
| Reported claim | Finding | Evidence |
|---|---|---|
| Go usage reports unauthenticated when credentials exist only in v2 storage. | Verified at collector boundary | The added regression fails with the exact unauthenticated result in both clean runs. |
| v2 stores credentials in a credential table. | Verified upstream schema | Read-only inspection of the upstream v2.0.18 tag confirms integration_id, JSON value, and active fields; API keys use type key. |
| The collector does not query that table. | Verified | Trusted base code only queries account and account_state; the v2-only test makes zero requests. |
| Legacy JSON credentials work around the lookup gap. | Verified with synthetic data | The legacy control passes using the same synthetic key and returns three windows. |
| A live v2 connection works in threads while UI and CLI usage fail. | Unverified end to end | No real provider session, service account, CLI request, or hosted usage endpoint was exercised. The exact production collector reached by usage collection was tested directly. |
3. Environment
- Trusted repository: public get-bb/bb; origin/main fetched from the canonical repository and pinned to
0e7b518f135d43005dae201ef34ebb3001607eb4. - Linux x86_64, kernel 4.19.0-gvisor; Node v22.19.0; pnpm 9.15.0; Vitest 4.1.1.
- OpenCode was not installed or launched for this reproduction. Its v2.0.18 tag resolves to
cd9a14a6b688d4021bee381dfd39d2cef9c0f862; upstream source was inspected, never executed. - Two distinct checkouts at the same trusted commit. Each test uses a fresh temporary SQLite database, with HOME and XDG_DATA_HOME explicitly set to the temporary directory.
- No BB instance, development ports, or production runtime data were needed. Only fetch was stubbed; SQLite and filesystem behavior are real.
4. Minimal reproduction
- Clone the trusted repository and check out the exact base commit:
git clone https://github.com/get-bb/bb.git bb-4387 cd bb-4387 git checkout --detach 0e7b518f135d43005dae201ef34ebb3001607eb4 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build
- Copy the complete regression test embedded in this report to
packages/provider-bridge-acp/src/bridge/opencode-usage.issue-4387.test.ts. It creates an active opencode-go key in a real SQLite database matching v2 storage; the old Console account tables are empty, and the first case has no legacy JSON file. - Run the owning package's tests with Turbo:
pnpm exec turbo run test --filter=@bb/provider-bridge-acp -- --reporter=verbose
- Expected: the v2-only case returns status ok and 5 hour, Weekly, and Monthly windows, making one stubbed request. Actual, verbatim from both runs:
{"scenario":"v2-only","result":{"supported":true,"usage":{"status":"unauthenticated"}},"requests":0} Test Files 1 failed | 19 passed (20) Tests 1 failed | 350 passed (351)The sole failure is the new v2 lookup regression. The legacy control and all 349 pre-existing tests pass. Exit code is 1, as expected for this diagnostic regression.
The test also verifies that the collector leaves the SQLite database byte-for-byte unchanged. All key values are deliberately synthetic; no key value is included in the result log.
Complete regression test
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { readOpenCodeGoUsage } from "./opencode-usage.js";
const syntheticKey = "synthetic-4387-not-a-real-key";
const usageWindow = {
status: "ok",
percent: 25,
resetsAt: "2026-10-01T00:00:00.000Z",
};
const fetchUsage = vi.fn<typeof fetch>();
let directory: string;
let databasePath: string;
let environment: NodeJS.ProcessEnv;
beforeEach(async () => {
directory = await fs.mkdtemp(path.join(os.tmpdir(), "slopcop-4387-data-"));
const dataDirectory = path.join(directory, "opencode");
await fs.mkdir(dataDirectory);
databasePath = path.join(dataDirectory, "opencode.db");
environment = { HOME: directory, XDG_DATA_HOME: directory };
const database = new DatabaseSync(databasePath);
try {
database.exec(
"CREATE TABLE account (id TEXT PRIMARY KEY, email TEXT NOT NULL, url TEXT NOT NULL, access_token TEXT NOT NULL, token_expiry INTEGER); CREATE TABLE account_state (id INTEGER PRIMARY KEY, active_account_id TEXT, active_org_id TEXT); CREATE TABLE credential (id TEXT PRIMARY KEY, integration_id TEXT, label TEXT NOT NULL, value TEXT NOT NULL, connector_id TEXT, method_id TEXT, active INTEGER, time_created INTEGER NOT NULL, time_updated INTEGER)",
);
database
.prepare(
"INSERT INTO credential (id, integration_id, label, value, active, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)",
)
.run(
"cred_synthetic4387",
"opencode-go",
"synthetic",
JSON.stringify({ type: "key", key: syntheticKey }),
1,
1,
1,
);
} finally {
database.close();
}
fetchUsage.mockReset();
fetchUsage.mockImplementation(async () =>
Response.json({
usage: {
rolling: usageWindow,
weekly: usageWindow,
monthly: usageWindow,
},
}),
);
vi.stubGlobal("fetch", fetchUsage);
});
afterEach(async () => {
vi.unstubAllGlobals();
await fs.rm(directory, { recursive: true, force: true });
});
it("collects usage with an active v2 SQLite key and no legacy credentials", async () => {
const originalDatabase = await fs.readFile(databasePath);
const result = await readOpenCodeGoUsage(environment);
expect(await fs.readFile(databasePath)).toEqual(originalDatabase);
console.log(
JSON.stringify({ scenario: "v2-only", result, requests: fetchUsage.mock.calls.length }),
);
expect(result).toMatchObject({
supported: true,
usage: {
status: "ok",
windows: [
{ label: "5 hour", usedPercent: 25 },
{ label: "Weekly", usedPercent: 25 },
{ label: "Monthly", usedPercent: 25 },
],
},
});
expect(fetchUsage).toHaveBeenCalledTimes(1);
});
it("collects the same usage when the synthetic key is present in legacy auth", async () => {
await fs.writeFile(
path.join(directory, "opencode", "auth.json"),
JSON.stringify({ "opencode-go": { type: "api", key: syntheticKey } }),
{ mode: 0o600 },
);
const result = await readOpenCodeGoUsage(environment);
console.log(
JSON.stringify({ scenario: "legacy-control", result, requests: fetchUsage.mock.calls.length }),
);
expect(result).toMatchObject({
supported: true,
usage: {
status: "ok",
windows: [
{ label: "5 hour", usedPercent: 25 },
{ label: "Weekly", usedPercent: 25 },
{ label: "Monthly", usedPercent: 25 },
],
},
});
expect(fetchUsage).toHaveBeenCalledTimes(1);
});
5. Root cause
The OpenCode branch in getAcpProviderUsage calls readOpenCodeGoUsage. The provider usage source preserves the collected status for consumers; it does not discover provider credentials itself.
readAccount opens SQLite read-only but only joins account and account_state. Empty account tables return null, regardless of any active v2 credential. readApiKey only considers OPENCODE_API_KEY, OPENCODE_AUTH_CONTENT, and auth.json. With those absent, the collector returns unauthenticated before fetch:
account = env.OPENCODE_API_KEY?.trim() ? null : await readAccount(env);
apiKey = account?.access_token ?? (await readApiKey(env));
if (apiKey === null)
return { supported: true, usage: { status: "unauthenticated" } };
This is credential discovery incompatibility, not a rejected token or a network error: the v2-only run makes zero requests. Upstream's credential schema stores integration identity, an active flag, and a JSON value. Its value schema distinguishes key credentials from OAuth credentials. The create path marks a newly created integration credential active. A future fix must handle the v2 key discriminator rather than applying the legacy api discriminator unchanged.
6. Proposed fix and automation decision
Add an explicitly validated, read-only v2 key lookup in this collector. Preserve existing environment/account/legacy precedence and endpoint restrictions. Restrict candidate rows to the intended Go or shared integration and the provider's active-selection semantics; do not leak keys or send unrelated/OAuth credentials to the usage service. Handle missing tables and malformed values deliberately, and retain legacy fallback behavior.
Regression coverage should include active versus inactive credentials, Go versus shared integration precedence, key versus OAuth values, invalid JSON, older databases, explicit environment overrides, active Console accounts, and byte-preserving reads. No dependency or migration is necessary to investigate this lookup.
No automatic pull request: extending the credential sources used to authenticate usage requests changes authentication handling, which this automation's simple-fix policy explicitly excludes. The report and failing regression are published for a separately authorized fix. No production changes were attempted, committed, or pushed.
7. Verification
The same investigator created a second clean temporary worktree at 0e7b518f135d43005dae201ef34ebb3001607eb4, checked its initially empty git status, copied only the new regression, and ran a frozen install. The exact command was:
pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run test --filter=@bb/provider-bridge-acp --force -- --reporter=verbose
The second run used newly created temporary data directories and forced execution rather than trusting a cached test result. It reproduced the same unauthenticated result, zero requests, and sole regression failure: 350 passing tests and 1 failing test. This is a repeated reproduction by the same agent, not an independent review. No report correction was needed. No ports were allocated.
The first trusted checkout's full build passed: 62 successful tasks, 62 total. Both package test runs passed all pre-existing tests. The repository was fetched again after reproduction; no newer collector fix appeared on origin/main. All BB code links above were checked against the pinned file line numbers. The report has no screenshots because this is a nonvisual credential-discovery failure.
8. Related issues and pull requests
Issue #1957 concerns usage visibility for this provider; #3499 is a different OpenCode compatibility problem, not this lookup defect. GitHub's issue cross-reference timeline contained no linked PR; an open-PR search for this issue also returned no results on September 30, 2026. No PR code was checked out or executed.
9. Appendix and trust boundary
First run: Test Files 1 failed | 19 passed (20)
Tests 1 failed | 350 passed (351)
Second run: Test Files 1 failed | 19 passed (20)
Tests 1 failed | 350 passed (351)
Base build: Tasks 62 successful, 62 total
The complete reproduction test and relevant output are embedded above. Raw logs and the standalone test are retained outside the public reports repository, in accordance with its publication policy.
Commands used: GitHub read-only issue/property/label/PR queries; fetch canonical main; detached checkout; frozen pnpm install; Turbo build and package tests; detached temporary worktree; read-only upstream schema inspection at a pinned release; git diff --check; and report publication. Original user credentials and unrelated files were not read.
Issue content was treated as untrusted claims. Its proposed implementation was not executed or copied. Test code was authored from trusted BB collector behavior and read-only upstream schema evidence. Type Bug, Priority Medium, Effort Medium, and existing area/plugin labels were preserved.