Skip to content

[bug] Coding CLIs driven through the terminal connector are completely unmetered — a tmux run reads as $0, not as unknown #348

Description

@serge-ivo

The gap

recordEngineUsage is called from exactly three places, all on the Coder path:

  • workers/api/src/workflows/coding-session.ts:347 and :645 (the Pilot)
  • workers/api/src/routes/coding.ts:691 and :1469 (capture / session end)

Every one is fed sanitizeEngineUsage(snap.usage), which originates in the stream-json result
event that packages/browser-runner/src/coding/headless.ts reads off the Claude Code child
process.

The terminal connector (packages/browser-runner/src/coding/terminal.ts, and the cloud-side
workers/api/src/lib/connectors/terminal.ts / tmux.ts) contains no token or cost
instrumentation at all
grep -n "usage\|cost\|token" returns nothing. It drives a pane with
terminal_send_keys and reads it back with terminal_capture; there is no structured event to
read a result from.

So a coding CLI running inside a tmux pane produces zero ai_usage rows. Not a low number —
no rows.

Why this matters right now

An A/B was just set up to compare the two drivers on the same repository:

driver engine tokens metered?
f8ddc272-0390-4826-8812-94989e3d2ebdHeartfull (Repo Coder) Pilot → stream-json child process ✅ yes, via result events
cda75e28-cace-4958-ac3e-6a7528e6b719Heartfull (tmux) (tmux Operator) chat agent → terminal_send_keys nothing

Run as-is, the Usage page will show the tmux side costing a small fraction of the other, and a
reasonable person will conclude tmux is cheaper. It isn't cheaper, it is invisible. Same Claude
Code binary, same repo, same context — the transport does not change what is sent to the API.

This is the identical failure the Usage page already warns about for Codex and Grok ("deliberately
absent rather than shown as zero
"), except here nothing warns about it, and it is about to be used
as the basis for a decision (#249).

What the vendor docs offer

From Manage costs effectively:

OpenTelemetry export works on every setup and is the only option that streams per-user token
and cost metrics into your own observability stack in near real time.

The Session block at the top of /usage shows detailed token usage statistics for your current
session… The figures are approximate and computed from local session history on this machine.

Two transport-independent meters, then: OTel (real-time, push) and the local session history that
/usage reads (after the fact, pull). Either measures the Claude Code process regardless of
whether PAGS talks to it over a pipe or a pane.

Fix

  1. Do not show an unmetered driver as $0. Minimum viable honesty, and it should land before
    any comparison is run: any session on a driver with no usage instrumentation is labelled
    unmetered, and its absence is stated on the Usage page the way Codex/Grok already are.
  2. Meter it properly. OTel is the option the vendor endorses and the only one that works
    across every engine and both drivers; reading the local session JSONL is the cheaper
    after-the-fact alternative. Either would also fix Codex/Grok, which are unmetered today for the
    same structural reason — no structured turn event.
  3. Whatever lands must attribute a payer ([design] The usage ledger records value, not charge — add payer attribution #346), or it reproduces the $50 problem on a new
    surface.

Also, while here — the Repo Coder tells users it uses tmux

The engine setting on coder-repo is described as:

"Which CLI this agent drives in tmux."

It does not. headless.ts spawns the CLI as a child process over stream-json; tmux belongs to the
terminal connector. The string is in the seeded settingsSchema and shows on every Repo Coder
instance (Heartfull, FWS platform, PAS platform, FAS platform, FGS platform). Same class of
problem as #347: a surface asserting something the code does not do, which then gets believed.

Verification

  • A tmux-driven coding session is either metered or explicitly labelled unmetered — never silently
    absent from a total.
  • The Usage page's "Not included" list names the terminal connector alongside Codex/Grok.
  • No settings copy claims the Repo Coder drives its CLI in tmux.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / Worker / API workbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions