fix(cost-tracker): prefer harness cost.total_cost_usd when statusline writes it

The transcript-sum estimate diverges from the harness's API-billed cost on
long sessions and after --resume:
- the hard-coded rate table cannot represent Opus 4.7's >200K-token 2x tier
  or the 1h-cache 2x tier (under-counts);
- summing the full transcript double-counts work across --resume boundaries
  while cost.total_cost_usd is per-process.

Both made the bridge file overshoot real cost by 4-5x, which tripped
ecc-context-monitor's $50 "critical" threshold and made agents halt
prematurely (observed: $73.99 hook value at $15.61 real harness cost).

This change adds an opt-in contract: if a statusline writes
{ts, cost_usd} to <tmpdir>/harness-cost-<session_id>.json on each render,
cost-tracker prefers that authoritative value when fresh (<=300s).
Behaviour is strictly additive — absent a writer, the transcript-sum path
is unchanged.

Tests cover both the prefer-fresh-cache and ignore-stale-cache paths.
This commit is contained in:
Robert Egoian
2026-05-25 20:29:56 +03:00
parent 1e8c7e7994
commit 46e2d58795
2 changed files with 137 additions and 1 deletions

View File

@@ -215,6 +215,93 @@ function runTests() {
fs.rmSync(tmpHome, { recursive: true, force: true });
}) ? passed++ : failed++);
// 8. Prefers harness-cost cache value over transcript-sum when fresh
(test('prefers fresh harness-cost cache over transcript estimate', () => {
const tmpHome = makeTempDir();
const sessionId = 'harness-fresh-' + Date.now();
const transcriptPath = path.join(tmpHome, 'session.jsonl');
writeTranscript(transcriptPath, [
{
type: 'assistant',
message: {
model: 'claude-opus-4-20250514',
usage: {
input_tokens: 10000,
output_tokens: 5000,
cache_creation_input_tokens: 200000,
cache_read_input_tokens: 1000000,
},
},
},
]);
const harnessCachePath = path.join(os.tmpdir(), `harness-cost-${sessionId}.json`);
const nowEpoch = Math.floor(Date.now() / 1000);
fs.writeFileSync(
harnessCachePath,
JSON.stringify({ ts: nowEpoch, cost_usd: 1.23 }),
'utf8'
);
try {
const result = runScript(
{ session_id: sessionId, transcript_path: transcriptPath },
withTempHome(tmpHome)
);
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl');
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
assert.strictEqual(row.estimated_cost_usd, 1.23, 'Expected harness cost to win');
// Token totals still reflect the transcript scan
assert.strictEqual(row.input_tokens, 10000, 'Token totals should still come from transcript');
assert.strictEqual(row.output_tokens, 5000, 'Token totals should still come from transcript');
} finally {
try { fs.unlinkSync(harnessCachePath); } catch { /* best-effort */ }
fs.rmSync(tmpHome, { recursive: true, force: true });
}
}) ? passed++ : failed++);
// 9. Ignores stale harness-cost cache and falls back to transcript estimate
(test('ignores stale harness-cost cache (>300s) and uses transcript estimate', () => {
const tmpHome = makeTempDir();
const sessionId = 'harness-stale-' + Date.now();
const transcriptPath = path.join(tmpHome, 'session.jsonl');
writeTranscript(transcriptPath, [
{
type: 'assistant',
message: {
model: 'claude-sonnet-4-20250514',
usage: { input_tokens: 1000, output_tokens: 500 },
},
},
]);
const harnessCachePath = path.join(os.tmpdir(), `harness-cost-${sessionId}.json`);
const staleEpoch = Math.floor(Date.now() / 1000) - 3600;
fs.writeFileSync(
harnessCachePath,
JSON.stringify({ ts: staleEpoch, cost_usd: 999.99 }),
'utf8'
);
try {
const result = runScript(
{ session_id: sessionId, transcript_path: transcriptPath },
withTempHome(tmpHome)
);
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl');
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
assert.notStrictEqual(row.estimated_cost_usd, 999.99, 'Stale cache must not win');
assert.ok(row.estimated_cost_usd > 0, 'Expected fallback transcript estimate to be positive');
// Sonnet rates: 1000/1e6*3 + 500/1e6*15 ≈ $0.011 — well below the 999.99 stale value
assert.ok(row.estimated_cost_usd < 1, 'Expected small transcript estimate, not the stale 999.99');
} finally {
try { fs.unlinkSync(harnessCachePath); } catch { /* best-effort */ }
fs.rmSync(tmpHome, { recursive: true, force: true });
}
}) ? passed++ : failed++);
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}