perf(metrics): cache cumulative session costs

This commit is contained in:
wellkilo
2026-09-13 01:52:26 +08:00
parent 8321021c54
commit e1805d7deb
7 changed files with 672 additions and 19 deletions
+10 -1
View File
@@ -4,7 +4,9 @@
*
* Reads transcript_path from Stop hook stdin, sums usage across all
* assistant turns in the session JSONL, and appends one row to
* ~/.claude/metrics/costs.jsonl.
* ~/.claude/metrics/costs.jsonl. It also atomically publishes the latest
* cumulative row under metrics/cost-snapshots/ so frequent PostToolUse
* hooks do not need to rescan the unbounded history.
*
* Stop hook stdin payload: { session_id, transcript_path, cwd, hook_event_name, ... }
* The Stop payload does NOT include `usage` or `model` directly. The previous
@@ -42,6 +44,7 @@ const os = require('os');
const path = require('path');
const { ensureDir, appendFile, getClaudeDir } = require('../lib/utils');
const { sanitizeSessionId } = require('../lib/session-bridge');
const { publishAppendedSessionCostSnapshot } = require('../lib/session-cost-snapshot');
const HARNESS_COST_MAX_AGE_SECONDS = 300;
@@ -243,6 +246,12 @@ process.stdin.on('end', () => {
};
appendFile(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
try {
publishAppendedSessionCostSnapshot(metricsDir, sessionId, row);
} catch {
// The append-only log remains authoritative. A later bridge read falls
// back to it when an atomic snapshot cannot be published.
}
} catch {
// Non-blocking — never fail the Stop hook.
}
+56 -18
View File
@@ -14,6 +14,13 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const { sanitizeSessionId, readBridge, writeBridgeAtomic } = require('../lib/session-bridge');
const {
getCostLogSignature,
isValidCostRow,
readSessionCostSnapshot,
repairSessionCostSnapshot,
signaturesMatch,
} = require('../lib/session-cost-snapshot');
const { getClaudeDir } = require('../lib/utils');
const MAX_STDIN = 1024 * 1024;
@@ -134,41 +141,55 @@ function writeCostWarningIfChanged(kind, costsPath, signature, message) {
}
/**
* Read cumulative cost for a session from costs.jsonl.
* Read cumulative cost for a session.
*
* Scans the full file because each row is a cumulative session total
* (see cost-tracker.js docblock) and the row we need is the last one
* matching `sessionId`. The previous implementation read only the
* trailing 8 KiB; any session whose latest cumulative row was pushed
* past that window by newer rows from other sessions silently dropped
* to zero — the opposite sign of the double-count bug fixed in the
* previous commit.
* The Stop hook publishes an atomic per-session snapshot, so the normal
* PostToolUse path reads O(1) data instead of reparsing unbounded history.
* Older ECC installations and damaged/missing snapshots remain compatible:
* they fall back to scanning costs.jsonl for the last cumulative row.
*
* costs.jsonl is append-only and unbounded today (no rotation in
* cost-tracker.js). At a typical ~150 bytes per row, even 100k rows
* is ~15 MB and a single sync read on every PostToolUse hook is in
* the low milliseconds. If rotation lands later, this scan becomes
* even cheaper.
* The fallback deliberately scans the whole file. A fixed tail window loses
* sessions whose newest row has been pushed back by other sessions.
*/
function readSessionCost(sessionId) {
let costsPath = path.join('metrics', 'costs.jsonl');
try {
costsPath = path.join(getClaudeDir(), 'metrics', 'costs.jsonl');
const metricsDir = path.join(getClaudeDir(), 'metrics');
const snapshot = readSessionCostSnapshot(metricsDir, sessionId);
if (snapshot) {
return {
totalCost: toNumber(snapshot.estimated_cost_usd),
totalIn: toNumber(snapshot.input_tokens),
totalOut: toNumber(snapshot.output_tokens)
};
}
costsPath = path.join(metricsDir, 'costs.jsonl');
const sourceBefore = getCostLogSignature(metricsDir);
const content = fs.readFileSync(costsPath, 'utf8');
const lines = content.split('\n').filter(Boolean);
let totalCost = 0;
let totalIn = 0;
let totalOut = 0;
let latestRow = null;
let malformed = 0;
let invalid = 0;
const malformedHasher = crypto.createHash('sha256');
const invalidHasher = crypto.createHash('sha256');
for (const line of lines) {
try {
const row = JSON.parse(line);
if (row.session_id === sessionId) {
totalCost = toNumber(row.estimated_cost_usd);
totalIn = toNumber(row.input_tokens);
totalOut = toNumber(row.output_tokens);
if (isValidCostRow(row, sessionId)) {
latestRow = row;
totalCost = row.estimated_cost_usd;
totalIn = row.input_tokens;
totalOut = row.output_tokens;
} else {
invalid += 1;
invalidHasher.update(line).update('\0');
}
}
} catch {
malformed += 1;
@@ -187,6 +208,23 @@ function readSessionCost(sessionId) {
`[ecc-metrics-bridge] skipped ${malformed} malformed line(s) in ${costsPath}\n`
);
}
if (invalid > 0) {
writeCostWarningIfChanged(
'invalid-row',
costsPath,
`${invalid}:${invalidHasher.digest('hex').slice(0, 16)}`,
`[ecc-metrics-bridge] skipped ${invalid} invalid cumulative row(s) for ${sessionId} in ${costsPath}\n`
);
}
const sourceAfter = getCostLogSignature(metricsDir);
if (latestRow && signaturesMatch(sourceBefore, sourceAfter)) {
try {
repairSessionCostSnapshot(metricsDir, sessionId, latestRow, sourceAfter);
} catch {
// Snapshot repair is best effort; the JSONL result remains valid.
}
}
return { totalCost, totalIn, totalOut };
} catch (err) {
// ENOENT is the common case (no Stop event has fired yet this session)
@@ -259,7 +297,7 @@ function run(rawInput) {
if (recent.length > RECENT_TOOLS_SIZE) recent.shift();
bridge.recent_tools = recent;
// Update cost from costs.jsonl tail
// Use the O(1) session snapshot, with JSONL compatibility fallback.
const costs = readSessionCost(sessionId);
bridge.total_cost_usd = Math.round(costs.totalCost * 1e6) / 1e6;
bridge.total_input_tokens = costs.totalIn;
+147
View File
@@ -0,0 +1,147 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { writeFileAtomic } = require('./atomic-write');
const { sanitizeSessionId } = require('./session-bridge');
const COST_SNAPSHOT_SCHEMA_VERSION = 'ecc.cost-snapshot.v1';
const COST_SNAPSHOT_DIRECTORY = 'cost-snapshots';
const COST_LOG_FILENAME = 'costs.jsonl';
function assertSafeSessionId(sessionId) {
if (sanitizeSessionId(sessionId) !== sessionId) {
throw new Error('Cost snapshot requires a safe session ID');
}
}
function getCostSnapshotPath(metricsDir, sessionId) {
assertSafeSessionId(sessionId);
// Prefix the filename so Windows device names such as CON/NUL/COM1 never
// become the basename, even when they are otherwise valid session IDs.
return path.join(metricsDir, COST_SNAPSHOT_DIRECTORY, `session-${sessionId}.json`);
}
function getCostLogSignature(metricsDir) {
const stat = fs.statSync(path.join(metricsDir, COST_LOG_FILENAME));
return {
size_bytes: stat.size,
mtime_ms: stat.mtimeMs
};
}
function signaturesMatch(left, right) {
return left?.size_bytes === right?.size_bytes
&& left?.mtime_ms === right?.mtime_ms;
}
function isValidCostRow(row, sessionId) {
return row?.session_id === sessionId
&& typeof row.estimated_cost_usd === 'number'
&& Number.isFinite(row.estimated_cost_usd)
&& row.estimated_cost_usd >= 0
&& typeof row.input_tokens === 'number'
&& Number.isFinite(row.input_tokens)
&& row.input_tokens >= 0
&& typeof row.output_tokens === 'number'
&& Number.isFinite(row.output_tokens)
&& row.output_tokens >= 0;
}
function assertCostLogSignature(source) {
if (!Number.isSafeInteger(source?.size_bytes) || source.size_bytes < 0) {
throw new Error('Cost snapshot requires a valid source size');
}
if (!Number.isFinite(source?.mtime_ms) || source.mtime_ms < 0) {
throw new Error('Cost snapshot requires a valid source mtime');
}
}
function writeSnapshotForSource(metricsDir, sessionId, row, source) {
assertSafeSessionId(sessionId);
if (!isValidCostRow(row, sessionId)) {
throw new Error('Cost snapshot requires valid non-negative numeric totals for its session');
}
const snapshotPath = getCostSnapshotPath(metricsDir, sessionId);
assertCostLogSignature(source);
return writeFileAtomic(
snapshotPath,
JSON.stringify({
schema_version: COST_SNAPSHOT_SCHEMA_VERSION,
source,
row
}),
{
beforeRename() {
if (!signaturesMatch(source, getCostLogSignature(metricsDir))) {
throw new Error('Cost log changed while publishing its session snapshot');
}
}
}
);
}
function costLogEndsWithRow(metricsDir, row) {
const expected = Buffer.from(`${JSON.stringify(row)}\n`, 'utf8');
const descriptor = fs.openSync(path.join(metricsDir, COST_LOG_FILENAME), 'r');
try {
const stat = fs.fstatSync(descriptor);
if (stat.size < expected.length) return false;
const actual = Buffer.allocUnsafe(expected.length);
const bytesRead = fs.readSync(
descriptor,
actual,
0,
expected.length,
stat.size - expected.length
);
return bytesRead === expected.length && actual.equals(expected);
} finally {
fs.closeSync(descriptor);
}
}
function publishAppendedSessionCostSnapshot(metricsDir, sessionId, row) {
assertSafeSessionId(sessionId);
if (!isValidCostRow(row, sessionId)) {
throw new Error('Cost snapshot requires valid non-negative numeric totals for its session');
}
const sourceBefore = getCostLogSignature(metricsDir);
if (!costLogEndsWithRow(metricsDir, row)) return false;
const sourceAfter = getCostLogSignature(metricsDir);
if (!signaturesMatch(sourceBefore, sourceAfter)) return false;
writeSnapshotForSource(metricsDir, sessionId, row, sourceAfter);
return true;
}
function repairSessionCostSnapshot(metricsDir, sessionId, row, source) {
writeSnapshotForSource(metricsDir, sessionId, row, source);
}
function readSessionCostSnapshot(metricsDir, sessionId) {
try {
const snapshot = JSON.parse(
fs.readFileSync(getCostSnapshotPath(metricsDir, sessionId), 'utf8')
);
if (snapshot?.schema_version !== COST_SNAPSHOT_SCHEMA_VERSION) return null;
if (!isValidCostRow(snapshot.row, sessionId)) return null;
if (!signaturesMatch(snapshot.source, getCostLogSignature(metricsDir))) return null;
return snapshot.row;
} catch {
return null;
}
}
module.exports = {
COST_SNAPSHOT_SCHEMA_VERSION,
COST_SNAPSHOT_DIRECTORY,
COST_LOG_FILENAME,
getCostSnapshotPath,
getCostLogSignature,
signaturesMatch,
isValidCostRow,
publishAppendedSessionCostSnapshot,
repairSessionCostSnapshot,
readSessionCostSnapshot
};
+5
View File
@@ -17,6 +17,11 @@ The tracker appends one JSON object per session-stop to
session**, so to total spend you take the **latest row per `session_id`** and
sum across sessions — summing every row multiply-counts.
ECC also maintains internal per-session files under
`~/.claude/metrics/cost-snapshots/` so runtime hooks can read the current
session total without rescanning all history. Treat those files as a
rebuildable cache; reports and exports should continue to use `costs.jsonl`.
Row schema:
| Field | Meaning |
+34
View File
@@ -9,6 +9,7 @@ const path = require('path');
const fs = require('fs');
const os = require('os');
const { spawnSync } = require('child_process');
const { getCostSnapshotPath } = require('../../scripts/lib/session-cost-snapshot');
const script = path.join(__dirname, '..', '..', 'scripts', 'hooks', 'cost-tracker.js');
@@ -115,6 +116,32 @@ function runTests() {
assert.strictEqual(result.stdout, inputStr, 'Expected stdout to match original input');
}) ? passed++ : failed++);
(test('keeps JSONL authoritative when the snapshot path cannot be published', () => {
const tmpHome = makeTempDir();
const metricsDir = path.join(tmpHome, '.claude', 'metrics');
const blockedSnapshotPath = path.join(
metricsDir,
'cost-snapshots',
'snapshot-failure.json'
);
fs.mkdirSync(blockedSnapshotPath, { recursive: true });
try {
const result = runScript(
{ session_id: 'snapshot-failure' },
withTempHome(tmpHome)
);
assert.strictEqual(result.code, 0, result.stderr);
const rows = fs.readFileSync(path.join(metricsDir, 'costs.jsonl'), 'utf8')
.trim()
.split('\n')
.map(line => JSON.parse(line));
assert.strictEqual(rows.at(-1).session_id, 'snapshot-failure');
} finally {
fs.rmSync(tmpHome, { recursive: true, force: true });
}
}) ? passed++ : failed++);
// 2. Creates metrics file when given transcript usage data
(test('creates metrics file when given transcript usage data', () => {
const tmpHome = makeTempDir();
@@ -154,6 +181,7 @@ function runTests() {
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl');
const metricsDir = path.dirname(metricsFile);
assert.ok(fs.existsSync(metricsFile), `Expected metrics file to exist at ${metricsFile}`);
const content = fs.readFileSync(metricsFile, 'utf8').trim();
@@ -169,6 +197,12 @@ function runTests() {
assert.ok(typeof row.estimated_cost_usd === 'number', 'Expected estimated_cost_usd to be a number');
assert.ok(row.estimated_cost_usd > 0, 'Expected estimated_cost_usd to be positive');
const snapshotFile = getCostSnapshotPath(metricsDir, 'session-from-hook');
assert.ok(fs.existsSync(snapshotFile), 'Expected an O(1) per-session cost snapshot');
const snapshot = JSON.parse(fs.readFileSync(snapshotFile, 'utf8'));
assert.strictEqual(snapshot.schema_version, 'ecc.cost-snapshot.v1');
assert.deepStrictEqual(snapshot.row, row, 'Snapshot must mirror the appended cumulative row');
fs.rmSync(tmpHome, { recursive: true, force: true });
}) ? passed++ : failed++);
+250
View File
@@ -11,6 +11,10 @@ const os = require('os');
const path = require('path');
const { run, hashToolCall, extractFilePaths, readSessionCost } = require('../../scripts/hooks/ecc-metrics-bridge');
const {
getCostSnapshotPath,
publishAppendedSessionCostSnapshot
} = require('../../scripts/lib/session-cost-snapshot');
// Test helper
function test(name, fn) {
@@ -233,6 +237,252 @@ function runTests() {
passed++;
else failed++;
if (
test('readSessionCost uses the per-session snapshot without scanning historical JSONL', () => {
const tmpHome = makeTempHome();
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const originalReadFileSync = fs.readFileSync;
try {
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
const metricsDir = path.join(tmpHome, '.claude', 'metrics');
const snapshotsDir = path.join(metricsDir, 'cost-snapshots');
fs.mkdirSync(snapshotsDir, { recursive: true });
const snapshotRow = {
session_id: 'S1',
estimated_cost_usd: 0.75,
input_tokens: 750,
output_tokens: 375
};
fs.writeFileSync(
path.join(metricsDir, 'costs.jsonl'),
`${JSON.stringify(snapshotRow)}\n`,
'utf8'
);
assert.strictEqual(
publishAppendedSessionCostSnapshot(metricsDir, 'S1', snapshotRow),
true
);
fs.readFileSync = function guardedRead(filePath, ...args) {
if (path.basename(String(filePath)) === 'costs.jsonl') {
throw new Error('historical JSONL scan should be bypassed on a snapshot hit');
}
return originalReadFileSync.call(this, filePath, ...args);
};
const result = readSessionCost('S1');
assert.deepStrictEqual(result, { totalCost: 0.75, totalIn: 750, totalOut: 375 });
} finally {
fs.readFileSync = originalReadFileSync;
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = originalUserProfile;
fs.rmSync(tmpHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
test('readSessionCost ignores a stale snapshot after costs.jsonl advances', () => {
const tmpHome = makeTempHome();
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
try {
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
const metricsDir = path.join(tmpHome, '.claude', 'metrics');
fs.mkdirSync(metricsDir, { recursive: true });
const first = {
session_id: 'S1',
estimated_cost_usd: 1,
input_tokens: 100,
output_tokens: 50
};
const latest = {
session_id: 'S1',
estimated_cost_usd: 2,
input_tokens: 200,
output_tokens: 100
};
const costsPath = path.join(metricsDir, 'costs.jsonl');
fs.writeFileSync(costsPath, `${JSON.stringify(first)}\n`, 'utf8');
publishAppendedSessionCostSnapshot(metricsDir, 'S1', first);
fs.appendFileSync(costsPath, `${JSON.stringify(latest)}\n`, 'utf8');
assert.deepStrictEqual(readSessionCost('S1'), {
totalCost: 2,
totalIn: 200,
totalOut: 100
});
const originalReadFileSync = fs.readFileSync;
fs.readFileSync = function guardedRead(filePath, ...args) {
if (path.basename(String(filePath)) === 'costs.jsonl') {
throw new Error('fallback should repair the session snapshot');
}
return originalReadFileSync.call(this, filePath, ...args);
};
try {
assert.deepStrictEqual(readSessionCost('S1'), {
totalCost: 2,
totalIn: 200,
totalOut: 100
});
} finally {
fs.readFileSync = originalReadFileSync;
}
} finally {
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = originalUserProfile;
fs.rmSync(tmpHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
test('readSessionCost falls back to JSONL when the session snapshot is malformed', () => {
const tmpHome = makeTempHome();
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
try {
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
const metricsDir = path.join(tmpHome, '.claude', 'metrics');
const snapshotsDir = path.join(metricsDir, 'cost-snapshots');
fs.mkdirSync(snapshotsDir, { recursive: true });
fs.writeFileSync(getCostSnapshotPath(metricsDir, 'S1'), '{broken', 'utf8');
fs.writeFileSync(
path.join(metricsDir, 'costs.jsonl'),
`${JSON.stringify({
session_id: 'S1',
estimated_cost_usd: 1.5,
input_tokens: 1500,
output_tokens: 750
})}\n`,
'utf8'
);
assert.deepStrictEqual(readSessionCost('S1'), {
totalCost: 1.5,
totalIn: 1500,
totalOut: 750
});
} finally {
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = originalUserProfile;
fs.rmSync(tmpHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
test('readSessionCost falls back when a snapshot row has invalid numeric totals', () => {
const tmpHome = makeTempHome();
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
try {
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
const metricsDir = path.join(tmpHome, '.claude', 'metrics');
const snapshotsDir = path.join(metricsDir, 'cost-snapshots');
fs.mkdirSync(snapshotsDir, { recursive: true });
const valid = {
session_id: 'S1',
estimated_cost_usd: 2,
input_tokens: 200,
output_tokens: 100
};
const costsPath = path.join(metricsDir, 'costs.jsonl');
fs.writeFileSync(costsPath, `${JSON.stringify(valid)}\n`, 'utf8');
const stat = fs.statSync(costsPath);
fs.writeFileSync(
getCostSnapshotPath(metricsDir, 'S1'),
JSON.stringify({
schema_version: 'ecc.cost-snapshot.v1',
source: { size_bytes: stat.size, mtime_ms: stat.mtimeMs },
row: { session_id: 'S1' }
}),
'utf8'
);
assert.deepStrictEqual(readSessionCost('S1'), {
totalCost: 2,
totalIn: 200,
totalOut: 100
});
} finally {
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = originalUserProfile;
fs.rmSync(tmpHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
test('readSessionCost skips invalid cumulative JSONL rows after the last valid total', () => {
const tmpHome = makeTempHome();
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const originalStderrWrite = process.stderr.write.bind(process.stderr);
let captured = '';
process.stderr.write = chunk => {
captured += String(chunk);
return true;
};
try {
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
const metricsDir = path.join(tmpHome, '.claude', 'metrics');
fs.mkdirSync(metricsDir, { recursive: true });
const rows = [
{ session_id: 'S1', estimated_cost_usd: 2, input_tokens: 200, output_tokens: 100 },
{ session_id: 'S1', estimated_cost_usd: -999, input_tokens: 'invalid', output_tokens: -5 },
{ session_id: 'S1', input_tokens: 300, output_tokens: 150 },
{ session_id: 'S1', estimated_cost_usd: null, input_tokens: 400, output_tokens: 200 },
{ session_id: 'OTHER', estimated_cost_usd: -1, input_tokens: -1, output_tokens: -1 }
];
fs.writeFileSync(
path.join(metricsDir, 'costs.jsonl'),
`${rows.map(row => JSON.stringify(row)).join('\n')}\n`,
'utf8'
);
assert.deepStrictEqual(readSessionCost('S1'), {
totalCost: 2,
totalIn: 200,
totalOut: 100
});
assert.match(captured, /skipped 3 invalid cumulative row\(s\) for S1/);
} finally {
process.stderr.write = originalStderrWrite;
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = originalUserProfile;
fs.rmSync(tmpHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
if (
test('readSessionCost finds session row beyond the old 8 KiB tail boundary', () => {
// The previous implementation read only the trailing 8 KiB of
+170
View File
@@ -0,0 +1,170 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const {
COST_SNAPSHOT_SCHEMA_VERSION,
getCostSnapshotPath,
publishAppendedSessionCostSnapshot,
readSessionCostSnapshot,
} = require('../../scripts/lib/session-cost-snapshot');
function test(name, fn) {
try {
fn();
console.log(` PASS ${name}`);
return true;
} catch (error) {
console.log(` FAIL ${name}`);
console.log(` ${error.message}`);
return false;
}
}
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-cost-snapshot-'));
const costLogPath = path.join(root, 'costs.jsonl');
let passed = 0;
let failed = 0;
try {
if (test('round-trips a versioned cumulative row atomically', () => {
const row = {
session_id: 'session-1',
estimated_cost_usd: 1.25,
input_tokens: 10,
output_tokens: 20
};
fs.writeFileSync(costLogPath, `${JSON.stringify(row)}\n`, 'utf8');
assert.strictEqual(publishAppendedSessionCostSnapshot(root, 'session-1', row), true);
const filePath = getCostSnapshotPath(root, 'session-1');
assert.strictEqual(filePath, getCostSnapshotPath(root, 'session-1'));
assert.deepStrictEqual(readSessionCostSnapshot(root, 'session-1'), row);
assert.deepStrictEqual(
fs.readdirSync(path.dirname(filePath)).filter(name => name.endsWith('.tmp')),
[]
);
})) passed++; else failed++;
if (test('replaces the previous cumulative row for the same session', () => {
const first = {
session_id: 'session-update',
estimated_cost_usd: 1,
input_tokens: 100,
output_tokens: 50
};
fs.writeFileSync(costLogPath, `${JSON.stringify(first)}\n`, 'utf8');
assert.strictEqual(publishAppendedSessionCostSnapshot(root, 'session-update', first), true);
const latest = {
session_id: 'session-update',
estimated_cost_usd: 2,
input_tokens: 200,
output_tokens: 100
};
fs.appendFileSync(costLogPath, `${JSON.stringify(latest)}\n`, 'utf8');
assert.strictEqual(publishAppendedSessionCostSnapshot(root, 'session-update', latest), true);
assert.deepStrictEqual(readSessionCostSnapshot(root, 'session-update'), latest);
})) passed++; else failed++;
if (test('invalidates a snapshot when the append-only cost log advances', () => {
const first = {
session_id: 'session-stale',
estimated_cost_usd: 1,
input_tokens: 100,
output_tokens: 50
};
fs.writeFileSync(costLogPath, `${JSON.stringify(first)}\n`, 'utf8');
assert.strictEqual(publishAppendedSessionCostSnapshot(root, 'session-stale', first), true);
fs.appendFileSync(
costLogPath,
`${JSON.stringify({ session_id: 'session-stale', estimated_cost_usd: 2, input_tokens: 200, output_tokens: 100 })}\n`,
'utf8'
);
assert.strictEqual(readSessionCostSnapshot(root, 'session-stale'), null);
})) passed++; else failed++;
if (test('a delayed older writer cannot overwrite the newest session row', () => {
const older = { session_id: 'session-race', estimated_cost_usd: 1, input_tokens: 100, output_tokens: 50 };
const newer = { session_id: 'session-race', estimated_cost_usd: 2, input_tokens: 200, output_tokens: 100 };
fs.writeFileSync(costLogPath, `${JSON.stringify(older)}\n`, 'utf8');
fs.appendFileSync(costLogPath, `${JSON.stringify(newer)}\n`, 'utf8');
assert.strictEqual(publishAppendedSessionCostSnapshot(root, 'session-race', newer), true);
assert.strictEqual(publishAppendedSessionCostSnapshot(root, 'session-race', older), false);
assert.deepStrictEqual(readSessionCostSnapshot(root, 'session-race'), newer);
})) passed++; else failed++;
if (test('rejects unsafe session IDs instead of escaping the snapshot directory', () => {
const unsafeId = '../outside';
const row = { session_id: unsafeId, estimated_cost_usd: 9, input_tokens: 9, output_tokens: 9 };
fs.writeFileSync(costLogPath, `${JSON.stringify(row)}\n`, 'utf8');
assert.throws(
() => publishAppendedSessionCostSnapshot(root, unsafeId, row),
/safe session ID/
);
const escapedPath = path.join(root, 'outside.json');
fs.writeFileSync(
escapedPath,
JSON.stringify({ schema_version: COST_SNAPSHOT_SCHEMA_VERSION, row }),
'utf8'
);
assert.strictEqual(readSessionCostSnapshot(root, unsafeId), null);
})) passed++; else failed++;
if (test('prefixes Windows reserved device names with a safe basename', () => {
assert.strictEqual(path.basename(getCostSnapshotPath(root, 'CON')), 'session-CON.json');
assert.strictEqual(path.basename(getCostSnapshotPath(root, 'nul')), 'session-nul.json');
})) passed++; else failed++;
if (test('rejects a snapshot whose row is bound to another session', () => {
const filePath = getCostSnapshotPath(root, 'session-2');
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(
filePath,
JSON.stringify({
schema_version: COST_SNAPSHOT_SCHEMA_VERSION,
row: { session_id: 'session-3', estimated_cost_usd: 3 }
}),
'utf8'
);
assert.strictEqual(readSessionCostSnapshot(root, 'session-2'), null);
})) passed++; else failed++;
if (test('rejects rows with missing, non-numeric, or negative totals', () => {
const invalidRows = [
{ session_id: 'invalid-row', input_tokens: 1, output_tokens: 1 },
{ session_id: 'invalid-row', estimated_cost_usd: '1', input_tokens: 1, output_tokens: 1 },
{ session_id: 'invalid-row', estimated_cost_usd: 1, input_tokens: -1, output_tokens: 1 },
{ session_id: 'invalid-row', estimated_cost_usd: 1, input_tokens: 1, output_tokens: Infinity }
];
for (const row of invalidRows) {
fs.writeFileSync(costLogPath, `${JSON.stringify(row)}\n`, 'utf8');
assert.throws(
() => publishAppendedSessionCostSnapshot(root, 'invalid-row', row),
/valid non-negative numeric totals/
);
}
})) passed++; else failed++;
if (test('rejects unknown schemas and malformed JSON', () => {
const filePath = getCostSnapshotPath(root, 'session-4');
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(
filePath,
JSON.stringify({
schema_version: 'ecc.cost-snapshot.v999',
row: { session_id: 'session-4' }
}),
'utf8'
);
assert.strictEqual(readSessionCostSnapshot(root, 'session-4'), null);
fs.writeFileSync(filePath, '{broken', 'utf8');
assert.strictEqual(readSessionCostSnapshot(root, 'session-4'), null);
})) passed++; else failed++;
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
console.log(`\nResults: ${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);