## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Cost/usage tracking is core to that: the dashboard shows per-agent
spend so a team can see what their AI workforce is costing them
> - The `grok_local` adapter (xAI's Grok Build CLI) is a newer adapter
than `claude_local`/`codex_local`, and its usage/cost wiring was left
incomplete
> - Every `grok_local` run persists
`usage.inputTokens/outputTokens/cachedInputTokens = 0` and `costUsd =
null` in `heartbeat_runs`, unconditionally, even though the underlying
`grok` CLI reports real, non-zero token counts and cost per turn in its
own JSON stream
> - This pull request wires the parser to actually read
`usage`/`total_cost_usd` from the CLI's terminal `end` event, threads
those values into the adapter's execution result, and marks them
`usageBasis: "per_run"` so the heartbeat service doesn't incorrectly
delta them against a prior run on a resumed session (matching how
`claude_local`/`codex_local` already do this)
> - The benefit is accurate cost/usage visibility for any self-hosted
Paperclip instance running Grok Build agents, instead of a dashboard
that always reads zero
## Linked Issues or Issue Description
Fixes: #10432
## What Changed
- `packages/adapters/grok-local/src/server/parse.ts`: `parseGrokJsonl()`
now reads `usage.input_tokens` / `usage.output_tokens` /
`usage.cache_read_input_tokens` / `total_cost_usd` from the terminal
`end` event and returns them on `ParsedGrokJsonl` (previously discarded
entirely).
- `packages/adapters/grok-local/src/server/execute.ts`: `toResult()` now
populates `usage.inputTokens/outputTokens/cachedInputTokens` from the
parsed values instead of hardcoded `0`, sets `usageBasis: "per_run"`
(each `--single` invocation reports usage for just that process, not a
running session total), and surfaces `costUsd` only when `billingType
=== "api"` (metered) — subscription/OAuth billing has no marginal dollar
cost, so it stays `null` there, but token counts are populated for both
billing types since usage visibility is useful regardless of billing
model.
- `packages/adapters/grok-local/src/server/parse.test.ts`: added a test
asserting usage/cost extraction from a representative `end` event
payload, and updated the existing exact-equality test for the new
fields.
- `packages/adapters/grok-local/src/server/execute.test.ts`: added a
test covering both subscription billing (tokens populated, `costUsd:
null`) and API-key billing (tokens populated, real `costUsd`), and
asserting `usageBasis: "per_run"` in both cases.
## Verification
- `pnpm vitest run packages/adapters/grok-local/src/server/parse.test.ts
packages/adapters/grok-local/src/server/execute.test.ts` — 9/9 passed
- `tsc --noEmit` on the `grok-local` package — clean
- Verified against a real self-hosted Paperclip instance running `grok`
CLI `0.2.112` with SuperGrok subscription (OAuth) auth: confirmed the
raw CLI stream reports real `usage`/`total_cost_usd` (e.g.
`"usage":{"input_tokens":21560,...},"total_cost_usd":0.0564448`) that
was previously discarded before ever reaching
`heartbeat_runs.usage_json`, which always showed all-zero tokens
regardless of real usage.
## Risks
- Low risk, additive change scoped entirely to the `grok_local`
adapter's usage/cost reporting path — no change to control flow, session
handling, or process execution.
- `usageBasis: "per_run"` mirrors the existing, already-tested pattern
in `claude_local`/`codex_local` execute paths, so the heartbeat
service's per-run vs. session-cumulative delta logic is exercised the
same way.
- `costUsd` is intentionally left `null` for subscription/OAuth billing
(no behavior change there beyond now-populated token counts) to avoid
implying a dollar cost that doesn't exist for flat-rate billing.
## Model Used
Claude Sonnet 5 (`claude-sonnet-5`), via Claude Code, no extended
thinking. Root cause was found by comparing real `grok` CLI JSON stream
output (captured directly from a live invocation) against the persisted
`heartbeat_runs.usage_json` row for the same run on a self-hosted
instance, then reading `parse.ts`/`execute.ts` source to confirm the
hardcoded zero values.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above (none found)
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change
(`fix/grok-local-usage-cost-tracking`) and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes (no
user-facing docs reference this internal usage-reporting behavior)
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green (pending at time of writing)
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(addressed the one P1 raised — `usageBasis: "per_run"`)
- [x] I will address all Greptile and reviewer comments before
requesting merge