Skip to content

fix(opencode): include cache read and write usage totals - #21886

Merged
nwparker merged 3 commits into
mainfrom
fix/opencode-cache-read-write-tokens
Sep 21, 2026
Merged

nwparker merged 3 commits into
mainfrom
fix/opencode-cache-read-write-tokens

Conversation

@nwparker

@nwparker nwparker commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​10 $\color{#cf222e}{\Huge{\mathbf{−}}}$​6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​4
Prod 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​41 $\color{#cf222e}{\Huge{\mathbf{−}}}$​5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​36

ELI5

OpenCode reports cache tokens separately. Orca was dropping those values in its session summary, so usage totals could be far too low.

What Changed

Session-level OpenCode usage now includes both cache-read and cache-write tokens in the serialized cache fields and total token count. Databases from older OpenCode versions without the write column continue to work, and write-only sessions are included.

Why

OpenCode's own usage model treats input, output, reasoning, cache-read, and cache-write as separate counters. Preserving all five keeps Orca's totals aligned with the source data and avoids losing usage during aggregate reads.

Linked Issue

Fixes #20186

Visual Proof

N/A — usage aggregation is a background data path with no UI layout or interaction change.

Testing

  • Automated tests added/updated, or explained why not below
  • I manually tested these changes locally

Validated with the OpenCode usage scanner suites (20 tests) and pnpm tc:node.

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable.

Notes

The change is local to Orca's SQLite usage reader and remains compatible with older databases. No remote protocol, SSH, mobile, or platform-specific process behavior changes.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (CI will cover remaining checks)

@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: ff9886bb-79d2-4891-a462-dec221fd766b

📥 Commits

Reviewing files that changed from the base of the PR and between 19ae781 and 1d4ca87.

📒 Files selected for processing (1)
  • src/main/opencode-usage/opencode-usage-row-queries.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/opencode-usage/opencode-usage-row-queries.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

OpenCode usage queries now include tokens_cache_write when available and serialize it in totals and cache data. OpenCode hook setup writes plugins to the resolved config discovery directory. Relay code adds canonical OpenCode installation, relay-overlay detection, and shared OMP extension handling. Tests and the localhost SSH check validate the updated paths and token totals.

Priority: ➖ Normal

Severity of issue fixed: Medium

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request also changes relay plugin installation and overlay behavior, OpenCode hook configuration, shared config-directory resolution, related tests, and the SSH localhost E2E test. The summar… Remove the unrelated relay, hook configuration, shared config-directory, and SSH E2E changes, or link the issue that requires them.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 18 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary objective: adding cache-read and cache-write tokens to OpenCode usage totals.
Description check ✅ Passed The description includes the required sections, explains the user impact and implementation, links issue #20186, documents testing, and explains why visual proof is not applicable. Some optional revie…
Linked Issues check ✅ Passed For #20186, the materialized-session query includes tokens_cache_read and optional tokens_cache_write in the positive-total filter. It selects tokens_cache_write as 0 when older databases lack…
Full details: Out of Scope Changes check

Explanation

The pull request also changes relay plugin installation and overlay behavior, OpenCode hook configuration, shared config-directory resolution, related tests, and the SSH localhost E2E test. The summary identifies these changes in src/relay/*, src/main/opencode/hook-service*, src/shared/opencode-config-directory.ts, and tests/e2e/ssh-localhost.spec.ts. These changes do not implement the usage aggregation requirements in #20186.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 1f81ddcc-d65e-4455-8adb-05b9811b94d5

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6d100 and 19ae781.

📒 Files selected for processing (11)
  • src/main/opencode-usage/opencode-usage-row-queries.ts
  • src/main/opencode-usage/scanner.test.ts
  • src/main/opencode/hook-service.test.ts
  • src/main/opencode/hook-service.ts
  • src/relay/omp-status-extension.ts
  • src/relay/opencode-canonical-config.ts
  • src/relay/plugin-overlay.test.ts
  • src/relay/plugin-overlay.ts
  • src/relay/relay-agent-hook-runtime.ts
  • src/shared/opencode-config-directory.ts
  • tests/e2e/ssh-localhost.spec.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


function getSessionTokenTotalExpression(db: Database.Database): string {
const cacheWrite = columnExists(db, 'session', 'tokens_cache_write')
? ' + tokens_cache_write'

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 \
  -e 'tokens_cache_write' \
  -e 'CREATE TABLE session' \
  -e 'ALTER TABLE session' \
  --glob '*.ts' --glob '*.sql' .

Repository: stablyai/orca

Length of output: 35206


Normalize nullable cache-write tokens before calculating totals.

If session.tokens_cache_write is NULL, SQLite makes the total expression NULL. The WHERE ... > 0 predicate then excludes sessions with other valid token usage. Use COALESCE(tokens_cache_write, 0) in the total expression and COALESCE(s.tokens_cache_write, 0) in the selected value.

env.ORCA_OPENCODE_SOURCE_CONFIG_DIR = sourceDir
}
} else {
this.pluginOverlay.installOpenCodePlugin(opencodeAgent, context.env)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Locate the source-directory resolver and the PTY environment merge contract.
resolver_file="$(rg -l --type ts '\bresolveOpenCodeSourceConfigDir\b' src | head -n 1)"
if [ -n "$resolver_file" ]; then
  ast-grep outline "$resolver_file" --items all --type function
  rg -n -C 12 --type ts '\bresolveOpenCodeSourceConfigDir\b|OPENCODE_CONFIG_DIR' "$resolver_file"
fi

rg -n -C 10 --type ts '\baddEnvAugmenter\s*\(|PtyEnvAugmenter|OPENCODE_CONFIG_DIR' src
rg -n -C 8 --type ts 'isRelayOverlayPath|\.orca-relay|installOpenCodePlugin' src/relay tests

Repository: stablyai/orca

Length of output: 45541


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- resolver declarations/usages ---'
rg -n -C 18 --type ts 'function resolveOpenCodeSourceConfigDir|const resolveOpenCodeSourceConfigDir|resolveOpenCodeSourceConfigDir\s*=|resolveOpenCodeSourceConfigDir\(' src
printf '%s\n' '--- canonical installer ---'
sed -n '1,180p' src/relay/opencode-canonical-config.ts
printf '%s\n' '--- runtime caller and context/env construction ---'
sed -n '1,150p' src/relay/relay-agent-hook-runtime.ts
rg -n -C 16 --type ts 'RelayAgentHookRuntime|new RelayAgentHookRuntime|addEnvAugmenter|envAugmenter|PtyEnvAugmenter' src/relay src/main

Repository: stablyai/orca

Length of output: 42664


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- canonical config implementation ---'
sed -n '1,150p' src/relay/opencode-canonical-config.ts
printf '%s\n' '--- config-directory resolver ---'
rg -n -C 20 --type ts 'resolveOpenCodeConfigDirectory' src/shared src/relay
printf '%s\n' '--- relay overlay predicate and materialization ---'
rg -n -C 18 --type ts 'isRelayOpenCodeOverlayPath|materializeOpenCode\(' src/relay/opencode-canonical-config.ts src/relay/plugin-overlay.ts

Repository: stablyai/orca

Length of output: 15248


Ensure relay-overlay launches use the selected plugin.

When sourceDir is an inherited OPENCODE_CONFIG_DIR under .orca-relay, this branch installs the selected plugin in the canonical config directory but returns no OPENCODE_CONFIG_DIR override. The child can therefore continue using the inherited relay overlay and load a stale plugin or the wrong OpenCode variant. Update the effective overlay or replace its config override after installation.

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

Important

The cache-token fix itself is correct, but the branch also carries fix(opencode): preserve global config discovery — a behavioral rewrite of OpenCode plugin installation that the PR description does not cover (it even states there are no SSH/platform changes). Two issues in that commit: a v1 opencode process now loads the OpenCode 2 plugin too, and the WSL guest path still overrides OPENCODE_CONFIG_DIR.

Reviewed changes

  • OpenCode cache-read/write in session totals — selectSessionUsageRows now reads tokens_cache_write, includes both cache counters in the serialized total, and the row-count query counts write-only sessions; DBs without the write column fall back to 0. cache-read-reproduction.test.ts already covers the missing-column path.
  • Status plugin installed into OpenCode's real config root — local OpenCodeHookService.buildPtyEnv and the SSH relay write orca-opencode-status.js / orca-opencode2-status.js into $XDG_CONFIG_HOME/opencode/plugins and stop setting OPENCODE_CONFIG_DIR; the overlay path is kept only when the user set a config dir.
  • Supporting extraction — new resolveOpenCodeConfigDirectory, installOpenCodePluginInCanonicalConfig / isRelayOpenCodeOverlayPath, and writeRelayOmpStatusExtension; the SSH e2e probe now checks the canonical root.

⚠️ WSL guest path still overrides OPENCODE_CONFIG_DIR with a plugin-only overlay

The config-discovery fix covers the local and SSH relay paths but leaves the WSL guest untouched. The host still points OPENCODE_CONFIG_DIR at the guest overlay (src/main/ipc/pty/host-env/assembly.ts:144-149), and createInstallPluginsHandler deliberately declines to mirror the default root (src/relay/wsl-install-plugins-handler.ts:29-33). Upstream resolves Global.config = Flag.OPENCODE_CONFIG_DIR ?? Path.config, so that env var replaces the XDG root rather than appending — a WSL user who has not set OPENCODE_CONFIG_DIR therefore gets a config root containing only Orca's plugin, and their ~/.config/opencode auth, models, keybinds, and plugins disappear. That is the exact failure the other two paths are being fixed for. The handler comment and wsl-install-plugins-handler.test.ts:176-194 encode the opposite ("APPENDS") premise.

Technical details
# WSL still replaces the guest's global OpenCode config root

## Affected sites
- `src/main/ipc/pty/host-env/assembly.ts:144-149` — sets `OPENCODE_CONFIG_DIR` to `getOpenCodeOverlayDir(...)` whenever the relay returned an overlay dir.
- `src/relay/wsl-install-plugins-handler.ts:29-33, 72-91` — materializes a plugin-only overlay when the guest has no explicit `OPENCODE_CONFIG_DIR`; the comment asserts OpenCode appends.
- `src/relay/wsl-install-plugins-handler.test.ts:176-194` — pins the "does not mirror the XDG default" behavior.

## Evidence
- `https://raw.githubusercontent.com/anomalyco/opencode/dev/packages/core/src/global.ts` — `config: Flag.OPENCODE_CONFIG_DIR ?? Path.config`.
- `https://raw.githubusercontent.com/anomalyco/opencode/dev/packages/core/src/flag/flag.ts` — `OPENCODE_CONFIG_DIR` is `process.env["OPENCODE_CONFIG_DIR"]`.
- `https://raw.githubusercontent.com/sindresorhus/xdg-basedir/main/index.js` — `xdgConfig` defaults to `~/.config` on every platform.

## Required outcome
- A WSL `opencode`/`opencode2` process launched without an explicit config dir must still read `~/.config/opencode`, with Orca's plugin added alongside it — the same contract the local/SSH paths now have.

## Open questions for the human
- Is WSL intentionally out of scope for this commit, or was it missed? If intentional, the "APPENDS" comment and the corresponding test should be corrected, since they document the opposite of upstream behavior.

ℹ️ The PR description describes only half the diff

The title, "What Changed", and "Notes" describe a change "local to Orca's SQLite usage reader" with "No remote protocol, SSH, mobile, or platform-specific process behavior changes." In fact the branch contains a second commit that changes SSH relay behavior, plugin installation on three host paths, and platform config discovery. A reviewer merging this cannot assess the risk from the description; either split the config-discovery commit into its own PR or document it here.

ℹ️ Nitpicks

  • src/main/opencode-usage/opencode-usage-row-queries.ts:103 — tokenTotalExpression.replaceAll('tokens_', 's.tokens_') builds aliased SQL by string substitution; passing an alias/prefix into getSessionTokenTotalExpression would make the coupling explicit instead of relying on every column starting with tokens_.
  • src/relay/omp-status-extension.ts:5,8,10 — hardcodes .orca-relay, omp-managed-status-extension, orca-agent-status.ts, and @orca-managed-pi-extension, duplicating the constants defined in plugin-overlay.ts; the two can drift.
  • src/main/opencode/hook-service.ts:124 — the local install resolves the config root from the Orca process env, while the relay passes the PTY env (context.env) into resolveOpenCodeConfigDirectory. If a shell rc exports a different XDG_CONFIG_HOME than the Orca main process sees, the write root and OpenCode's read root diverge.
  • src/relay/opencode-canonical-config.ts:21-26 unlinks the target before writing (symlink guard) while the local writePluginToConfigDir (src/main/opencode/hook-service.ts:259-263) does not; worth making the two consistent.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/relay/opencode-canonical-config.ts Resolved

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

ℹ️ No critical issues — one redundant lint suppression inline.

Reviewed changes (incremental)

This run covers the one commit added since the prior review at 19ae781:

  • fix(opencode): satisfy aggregate query safety checks (1d4ca87) — adds four oxlint-disable-next-line typescript/consistent-type-assertions directives, each with a SAFETY: rationale, around the SQLite .get() as { count?: number } / .all() as OpenCodeSessionUsageRow[] casts in src/main/opencode-usage/opencode-usage-row-queries.ts. No behavior change. The prior review's two findings (a v1 opencode process now loading the OpenCode 2 plugin, and the WSL guest path still overriding OPENCODE_CONFIG_DIR) are unaffected by this commit and remain open.

ℹ️ Nitpicks

  • src/main/opencode-usage/opencode-usage-row-queries.ts:50,81 — the SAFETY: rationale is identical boilerplate at each count cast ("validated by the typed count field below"), but row?.count ?? 0 validates nothing — the cast is unchecked. AGENTS.md asks for a line-specific explanation; one accurate rationale shared across the sites would carry more signal than the repeated claim.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

WHERE tokens_input + tokens_output + tokens_reasoning + tokens_cache_read > 0`
WHERE ${getSessionTokenTotalExpression(db)} > 0`
)
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: SQLite aggregate rows are validated by the typed count field below.

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.

oxlint reports this directive as unused: the rule reports the assertion at the statement start (const row = db), so the directive on line 81 already covers the cast on line 89. Removing this one keeps the casting scan clean — it is dead suppression.

@nwparker
nwparker force-pushed the fix/opencode-cache-read-write-tokens branch from 1d4ca87 to ec8929c Compare September 21, 2026 03:59
@nwparker
nwparker merged commit 87a22db into main Sep 21, 2026
2 checks passed

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

ℹ️ No new issues in the new commits — the branch was rebased and narrowed.

Reviewed changes

This run covers the delta since the prior pullfrog review at 1d4ca87 (which spanned 11 changed files):

  • Dropped the config-discovery commit — the plugin-installation rewrite that added src/relay/opencode-canonical-config.ts now lives in #21854, so this PR is back to the usage fix plus its tests.
  • Regenerated the runtime-required English catalog (ec8929c) — picks up the OpenCode Go session-cookie strings that #21462 added to en.json; node config/scripts/generate-runtime-required-english-catalog.mjs now reports the catalog covers en.json.
  • Usage fix unchanged — selectSessionUsageRows still reads tokens_cache_write, includes cache read and write in the serialized total, and the row-count query counts write-only sessions; scanner.test.ts asserts totalTokens: 1925 and the no-write-column path stays covered by cache-read-reproduction.test.ts.

The prior review's two substantive findings were about the dropped config-discovery commit, and its inline thread has been resolved as no longer part of this PR. The one remaining open thread is the earlier nitpick about the redundant oxlint-disable at opencode-usage-row-queries.ts:88, which is unchanged by these commits.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

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.

Stats & Usage drops OpenCode cache tokens, under-reporting OpenCode usage ~70x

1 participant