fix(miner): wire real dollar-cost tracking into the loop's budgetSpent#5356
Conversation
Advances #5135 loop-cli.js's GovernorCapUsage.budgetSpent was never incremented -- only turnsTaken/elapsedMs used real signals -- so a capLimits.budget dollar-cost dimension could never trip from the loop's own spend. Investigation found a real, already-flowing cost signal was being silently dropped: the Agent SDK's own result message declares `total_cost_usd: number` unconditionally (SDKResultSuccess and SDKResultError both carry it -- a session is billed whether or not it succeeds), but agent-sdk-driver.ts only ever read `num_turns`. Wires it through the whole real pipeline: - CodingAgentDriverResult gains an optional costUsd field. - agent-sdk-driver.ts extracts the SDK's real total_cost_usd whenever a result message arrived, on both the success and the "did not complete successfully" paths (never fabricated on a thrown/no-result stream, where no billing signal exists). - iterate-loop.ts sums it into a new totalCostUsd on IterateLoopResult, mirroring the existing totalTurnsUsed accumulator exactly. - attempt-cli.js surfaces it through options.onResult alongside totalTurnsUsed/iterationsUsed. - loop-cli.js increments usage.budgetSpent with the real value before calling governor-state.js's saveCapUsage. Documented, deliberate gap: the CLI-subprocess providers (claude-cli/ codex-cli) report no cost signal today -- their production argv never passes --output-format json, so there is nothing to parse cost from without a separate, larger change to how those providers invoke the real CLI binaries. budgetSpent is real for agent-sdk and an honest 0 (not fabricated) for the CLI-subprocess providers; a capLimits.budget dimension only ever meaningfully trips against agent-sdk spend.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 7c4221c | Commit Preview URL Branch Preview URL |
Jul 12 2026, 02:51 PM |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5356 +/- ##
==========================================
+ Coverage 94.40% 94.42% +0.01%
==========================================
Files 550 550
Lines 44117 44120 +3
Branches 14631 14633 +2
==========================================
+ Hits 41650 41660 +10
+ Misses 1792 1785 -7
Partials 675 675
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Caution 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🛑 Gittensory review result - fixes requiredReview updated: 2026-07-12 15:24:12 UTC
🛑 Suggested Action - Manual Review
Review summary Nits — 5 non-blocking
Why this is blocked
CI checks failing
Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Advances #5135 defaultCliSubprocessArgs built ["--max-turns", N, "--acceptance-criteria", path, instructions] -- neither --max-turns nor --acceptance-criteria is a real flag on the claude or codex CLI (verified against `claude --help` and `codex exec --help`, plus a live invocation), and the argv never included -p/--print (claude) or the exec subcommand (codex) at all. Without those, a spawned claude/codex process starts an INTERACTIVE session against a subprocess whose stdin is closed -- meaning the claude-cli/codex-cli providers had no real non-interactive argv shape in production. Splits the shared builder into defaultClaudeCliArgs (--print --output-format json --permission-mode acceptEdits) and defaultCodexCliArgs (exec --json --sandbox workspace-write), matching each CLI's own real, verified interface. --output-format json / --json also finally activate claudeErrorStatus/codexErrorFromStdout's existing JSON(L) parsing, which was already written for exactly this shape but never actually triggered since the flag was never passed. Surfaces two more real, previously-latent gaps this exposed: - claude's own documented behavior ("--output-format json sometimes exits non-zero") implies it can also exit 0 while the JSON envelope reports is_error: true -- confirmed empirically via a live invocation. claudeErrorStatus was already written to detect this exact shape but was only ever checked on the code!==0 branch; now also checked on success exit codes. - Adds real cost parsing (ported from src/selfhost/ai.ts's own COST_KEYS/extractCliUsage best-effort pattern) so claude-cli/codex-cli now report costUsd from their own real JSON(L) output too, closing the "CLI providers report no cost" gap #5356 documented. driver-factory.ts's buildCliArgsWithConfiguredModel is now command-aware: codex's own -m/--model flag is scoped to the exec subcommand (per codex exec --help) and must land after "exec", not prefixed before everything the way claude's top-level --model flag can be -- a single shared prefix-everything scheme would have silently misparsed for codex.
Summary
Follow-up to #5303/#5320:
loop-cli.js'sGovernorCapUsage.budgetSpentwas never incremented (onlyturnsTaken/elapsedMsused real signals), so acapLimits.budgetdollar-cost dimension could never trip from the loop's own spend.Investigation found a real, already-flowing cost signal was being silently dropped: the Agent SDK's own
resultmessage declarestotal_cost_usd: numberunconditionally (bothSDKResultSuccessandSDKResultErrorcarry it — a session is billed whether or not it succeeds), butagent-sdk-driver.tsonly ever readnum_turns.Changes
CodingAgentDriverResultgains an optionalcostUsdfield.agent-sdk-driver.tsextracts the SDK's realtotal_cost_usdwhenever a result message arrived, on both the success and the "did not complete successfully" paths (never fabricated on a thrown/no-result stream, where no billing signal exists at all).iterate-loop.tssums it into a newtotalCostUsdonIterateLoopResult, mirroring the existingtotalTurnsUsedaccumulator exactly (same 4 return points).attempt-cli.jssurfaces it throughoptions.onResultalongsidetotalTurnsUsed/iterationsUsed.loop-cli.jsincrementsusage.budgetSpentwith the real value before callinggovernor-state.js'ssaveCapUsage.Documented, deliberate gap
The CLI-subprocess providers (
claude-cli/codex-cli) report no cost signal today — their production argv never passes--output-format json, so there's nothing to parse cost from without a separate, larger change to how those providers invoke the real CLI binaries (a real scope decision, not a simple additive fix — I did not make that change here).budgetSpentis real foragent-sdkand an honest0(not fabricated) for the CLI-subprocess providers; acapLimits.budgetdimension only ever meaningfully trips againstagent-sdkspend today.Test plan
okDriverResult's realcostUsdflows throughrunMinerAttempt→result.loopResult.totalCostUsdtotalTurnsUsed/totalCostUsdloop-cli.js'sbudgetSpentreflects the realtotalCostUsdfrom a submitted attemptclaude-cli/codex-cli) leavesbudgetSpenthonestly at0across multiple real attemptscoding-agent-driver-parity.test.ts: documented-divergence tests locking in "CLI driver never reports cost" vs. "Agent-SDK driver reports the realtotal_cost_usd"npx vitest run test/unit— 735 files / 14,582 tests passnpm run test:coverage— verified patch coverage of every new/touched line via lcov (including the mid-loop abandon path, previously untested at the root level)npx tsc --noEmit -p . --incremental false(fresh, no cache) — cleannpm run test:engine-parity,npm run test:driver-parity,npm run test --workspace @jsonbored/gittensory-engine(520 node:test cases) — all passnpm run --prefix packages/gittensory-miner build— passgit diff --check— clean