Description
OpenUsage doesn't detect OpenCode after upgrading to OpenCode 2 (current stable 1.18.18 / 0.0.0-beta-17639 Go binary at ~/.opencode/bin/opencode). Menu bar shows Couldn't read OpenCode's local database and /v1/limits returns errors: [{providerId: "opencode", message: "Couldn't read OpenCode's local database..."}]. After the DB workaround the Go meters reappear but auth.json-based detection also fails.
Steps to Reproduce
- Install OpenCode 2 (new Go/TUI) via
npm i -g @opencode-ai/cli or curl -fsSL https://opencode.ai/install | bash -> binary ~/.opencode/bin/opencode version 1.18.18, data dir ~/.local/share/opencode/opencode.db
- Run a few sessions (
opencode run "hi"), confirm ~/.local/share/opencode/opencode.db has table session_message (not message) with rows where json_extract(data,'$.model.providerID')='opencode-go'
- Open OpenUsage
0.7.10-beta.1 -> OpenCode card shows error. curl http://127.0.0.1:6736/v1/limits and openusage --force both show the error. curl http://127.0.0.1:6736/v1/usage shows no OpenCode spend tiles.
- Check DB:
sqlite3 ~/.local/share/opencode/opencode.db "SELECT name FROM sqlite_master WHERE type='table';" -> shows session_message etc, no message. Check auth: ls ~/.local/share/opencode/auth.json -> missing (key is now in credential table).
Expected Behavior
OpenUsage detects OpenCode 2 local usage and shows:
- Go meters (Session/Weekly/Monthly from
https://opencode.ai/zen/go/v1/usage)
- Spend tiles Today / Last 30 Days + Usage Trend from local SQLite (combined Go+Zen), same as before the upgrade.
Actual Behavior
hasLocalCredentials() returns false via hasHostedUsage() probe because SELECT 1 FROM message ... fails with no such table: message
scan() throws databaseUnreadable for same reason
goAPIKey() returns nil because ~/.local/share/opencode/auth.json doesn't exist anymore; key is now in SQLite credential (SELECT json_extract(value,'$.key') FROM credential WHERE ... LIKE 'sk-%')
OpenUsage Version
0.7.10-beta.1 (523)
macOS Version
15.5+ (Sequoia, Apple Silicon)
Root Cause
Sources/OpenUsage/Providers/OpenCode/OpenCodeUsageScanner.swift:42 and Sources/OpenUsage/Providers/OpenCode/OpenCodePaths.swift:28 still target the OpenCode 1 schema:
- Table
message -> renamed to session_message in OpenCode 2 (see ~/.local/share/opencode/opencode.db schema: session_message(id, session_id, type, seq, time_created, time_updated, data))
- JSON shape changed:
$.role -> column type (assistant/user)
$.providerID -> $.model.providerID
$.modelID -> $.model.id
$.tokens.total -> COALESCE($.tokens.input,0)+COALESCE($.tokens.output,0)+COALESCE($.tokens.reasoning,0)+COALESCE($.tokens.cache.read,0)+COALESCE($.tokens.cache.write,0)
$.cost stays at top level but is now NULL for user/error rows (filtered by json_type IN ('integer','real'))
- Auth moved from
~/.local/share/opencode/auth.json ({"opencode-go":{"key":"sk-..."}}) to SQLite credential table. OpenCodeAuthStore.swift:40 only reads the file.
Strings extracted from binary confirm:
SELECT 1 FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_extract(data,'$.providerID') IN ('opencode-go','opencode') ...
SELECT json_group_array(json_array(time_created, json_extract(data,'$.cost'), COALESCE(json_extract(data,'$.tokens.total'),0), ...)) FROM message ...
Proposed Fix
- Scanner: probe for both tables, or create a compatibility VIEW. Suggested
dataSQL/probeSQL that union both schemas or try session_message first:
-- fallback that handles both v1 and v2
SELECT ... FROM (SELECT * FROM message UNION ALL SELECT * FROM session_message) -- if both exist
Or, detection: if session_message exists, query it with transformed columns:
CREATE VIEW IF NOT EXISTS message AS
SELECT time_created,
json_patch(json_patch(data, json_object('role', type)),
json_object('providerID', json_extract(data,'$.model.providerID'),
'modelID', json_extract(data,'$.model.id'),
'cost', json_extract(data,'$.cost'),
'tokens', json_object('total',
COALESCE(json_extract(data,'$.tokens.input'),0)+
COALESCE(json_extract(data,'$.tokens.output'),0)+
COALESCE(json_extract(data,'$.tokens.reasoning'),0)+
COALESCE(json_extract(data,'$.tokens.cache.read'),0)+
COALESCE(json_extract(data,'$.tokens.cache.write'),0))))
FROM session_message;
Better to make OpenCodeUsageScanner natively query session_message with the transformations, and keep message as fallback for old DBs. Also handle both $.tokens.total (v1) vs sum (v2) via COALESCE(json_extract(data,'$.tokens.total'), sum(...)).
- Auth: make
OpenCodeAuthStore.goAPIKey() try file first, then fallback to SQLite:
// if auth.json missing, open ~/.local/share/opencode/opencode*.db and query credential table
SELECT json_extract(value,'$.key') FROM credential WHERE json_extract(value,'$.key') LIKE 'sk-%' LIMIT 1
Respect OPENCODE_DATA_DIR/XDG_DATA_HOME via OpenCodePaths.dataDirectory.
- Tests: extend
OpenCodeUsageScannerTests to cover v2 row shape.
Workaround (verified locally)
Created VIEW + synced auth.json:
KEY=$(/usr/bin/sqlite3 ~/.local/share/opencode/opencode.db "SELECT json_extract(value,'\$.key') FROM credential WHERE json_extract(value,'\$.key') LIKE 'sk-%' LIMIT 1;")
echo "{\"opencode-go\":{\"key\":\"$KEY\"}}" > ~/.local/share/opencode/auth.json; chmod 600 ~/.local/share/opencode/auth.json
/usr/bin/sqlite3 ~/.local/share/opencode/opencode.db "DROP VIEW IF EXISTS message; CREATE VIEW message AS SELECT time_created, json_patch(... ) AS data FROM session_message;"
After that openusage --force shows errors: [], opencode with Today $0.20 · 21.4M and Trend, and /v1/usage lines are correct. LaunchAgent with WatchPaths on ~/.local/share/opencode keeps it persistent.
Happy to open a PR if you want - I have the SQL ready and tested against /usr/bin/sqlite3 3.51.0 (system) and ~/.local/share/opencode/opencode.db with 271 hosted rows.
Additional Context
/usr/bin/sqlite3 supports json_patch on macOS 15 (tested)
- Data dir resolution via
OPENCODE_DATA_DIR should stay, just add DB fallback
- No other providers affected
Description
OpenUsage doesn't detect OpenCode after upgrading to OpenCode 2 (current stable
1.18.18/0.0.0-beta-17639Go binary at~/.opencode/bin/opencode). Menu bar showsCouldn't read OpenCode's local databaseand/v1/limitsreturnserrors: [{providerId: "opencode", message: "Couldn't read OpenCode's local database..."}]. After the DB workaround the Go meters reappear butauth.json-based detection also fails.Steps to Reproduce
npm i -g @opencode-ai/cliorcurl -fsSL https://opencode.ai/install | bash-> binary~/.opencode/bin/opencodeversion1.18.18, data dir~/.local/share/opencode/opencode.dbopencode run "hi"), confirm~/.local/share/opencode/opencode.dbhas tablesession_message(notmessage) with rows wherejson_extract(data,'$.model.providerID')='opencode-go'0.7.10-beta.1-> OpenCode card shows error.curl http://127.0.0.1:6736/v1/limitsandopenusage --forceboth show the error.curl http://127.0.0.1:6736/v1/usageshows no OpenCode spend tiles.sqlite3 ~/.local/share/opencode/opencode.db "SELECT name FROM sqlite_master WHERE type='table';"-> showssession_messageetc, nomessage. Check auth:ls ~/.local/share/opencode/auth.json-> missing (key is now incredentialtable).Expected Behavior
OpenUsage detects OpenCode 2 local usage and shows:
https://opencode.ai/zen/go/v1/usage)Actual Behavior
hasLocalCredentials()returns false viahasHostedUsage()probe becauseSELECT 1 FROM message ...fails withno such table: messagescan()throwsdatabaseUnreadablefor same reasongoAPIKey()returns nil because~/.local/share/opencode/auth.jsondoesn't exist anymore; key is now in SQLitecredential(SELECT json_extract(value,'$.key') FROM credential WHERE ... LIKE 'sk-%')OpenUsage Version
0.7.10-beta.1 (523)
macOS Version
15.5+ (Sequoia, Apple Silicon)
Root Cause
Sources/OpenUsage/Providers/OpenCode/OpenCodeUsageScanner.swift:42andSources/OpenUsage/Providers/OpenCode/OpenCodePaths.swift:28still target the OpenCode 1 schema:message-> renamed tosession_messagein OpenCode 2 (see~/.local/share/opencode/opencode.dbschema:session_message(id, session_id, type, seq, time_created, time_updated, data))$.role-> columntype(assistant/user)$.providerID->$.model.providerID$.modelID->$.model.id$.tokens.total->COALESCE($.tokens.input,0)+COALESCE($.tokens.output,0)+COALESCE($.tokens.reasoning,0)+COALESCE($.tokens.cache.read,0)+COALESCE($.tokens.cache.write,0)$.coststays at top level but is nowNULLfor user/error rows (filtered byjson_type IN ('integer','real'))~/.local/share/opencode/auth.json({"opencode-go":{"key":"sk-..."}}) to SQLitecredentialtable.OpenCodeAuthStore.swift:40only reads the file.Strings extracted from binary confirm:
Proposed Fix
dataSQL/probeSQLthat union both schemas or trysession_messagefirst:Or, detection: if
session_messageexists, query it with transformed columns:Better to make
OpenCodeUsageScannernatively querysession_messagewith the transformations, and keepmessageas fallback for old DBs. Also handle both$.tokens.total(v1) vs sum (v2) viaCOALESCE(json_extract(data,'$.tokens.total'), sum(...)).OpenCodeAuthStore.goAPIKey()try file first, then fallback to SQLite:Respect
OPENCODE_DATA_DIR/XDG_DATA_HOMEviaOpenCodePaths.dataDirectory.OpenCodeUsageScannerTeststo cover v2 row shape.Workaround (verified locally)
Created VIEW + synced auth.json:
After that
openusage --forceshowserrors: [],opencodewith Today$0.20 · 21.4Mand Trend, and/v1/usagelines are correct. LaunchAgent withWatchPathson~/.local/share/opencodekeeps it persistent.Happy to open a PR if you want - I have the SQL ready and tested against
/usr/bin/sqlite33.51.0(system) and~/.local/share/opencode/opencode.dbwith 271 hosted rows.Additional Context
/usr/bin/sqlite3supportsjson_patchon macOS 15 (tested)OPENCODE_DATA_DIRshould stay, just add DB fallback