fix(hooks): suppress repeated metrics warning breadcrumbs

This commit is contained in:
Affaan Mustafa 2026-05-17 21:09:47 -04:00 committed by Affaan Mustafa
parent 2de0ce45d4
commit 4cafdb8304
2 changed files with 31 additions and 6 deletions

View File

@ -19,6 +19,7 @@ const MAX_STDIN = 1024 * 1024;
const MAX_FILES_TRACKED = 200;
const RECENT_TOOLS_SIZE = 5;
const HASH_INPUT_LIMIT = 2048;
const costWarningSignatures = new Map();
function toNumber(value) {
const n = Number(value);
@ -76,6 +77,13 @@ function extractFilePaths(toolName, toolInput) {
return paths;
}
function writeCostWarningOnce(kind, costsPath, signature, message) {
const key = `${kind}:${costsPath}`;
if (costWarningSignatures.get(key) === signature) return;
costWarningSignatures.set(key, signature);
process.stderr.write(message);
}
/**
* Read cumulative cost for a session from costs.jsonl.
*
@ -117,8 +125,15 @@ function readSessionCost(sessionId) {
}
// One aggregated breadcrumb per call rather than one per bad row, so a
// log-flooded costs.jsonl stays diagnosable without overwhelming stderr.
// Suppress repeats for the same malformed-line count; this hook runs after
// every tool invocation, so a persistent bad row should not spam stderr.
if (malformed > 0) {
process.stderr.write(`[ecc-metrics-bridge] skipped ${malformed} malformed line(s) in ${costsPath}\n`);
writeCostWarningOnce(
'malformed',
costsPath,
String(malformed),
`[ecc-metrics-bridge] skipped ${malformed} malformed line(s) in ${costsPath}\n`
);
}
return { totalCost, totalIn, totalOut };
} catch (err) {
@ -127,7 +142,12 @@ function readSessionCost(sessionId) {
// (permission, EISDIR, malformed read) deserves a breadcrumb because
// the bridge will silently report zero cost otherwise.
if (err && err.code !== 'ENOENT') {
process.stderr.write(`[ecc-metrics-bridge] failing open after ${err.name || 'error'} reading ${costsPath}: ${err.message || String(err)}\n`);
writeCostWarningOnce(
'read-error',
costsPath,
err.code || err.name || 'error',
`[ecc-metrics-bridge] failing open after ${err.name || 'error'} reading ${costsPath}: ${err.message || String(err)}\n`
);
}
return { totalCost: 0, totalIn: 0, totalOut: 0 };
}

View File

@ -226,11 +226,13 @@ function runTests() {
else failed++;
if (
test('readSessionCost writes a stderr breadcrumb when malformed lines are skipped', () => {
test('readSessionCost writes one stderr breadcrumb when malformed lines persist across calls', () => {
// Reviewer (coderabbitai) asked for diagnosability when the inner
// catch silently skips malformed JSON rows. Verify the aggregated
// "skipped N malformed line(s)" breadcrumb appears on stderr while
// the function still recovers the last valid matching row.
// the function still recovers the last valid matching row. Because
// this hook runs after every tool invocation, the same bad rows should
// not emit the same warning on every call.
const tmpHome = makeTempHome();
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
@ -257,8 +259,11 @@ function runTests() {
);
const result = readSessionCost('S1');
assert.strictEqual(result.totalCost, 0.7, 'last valid row should still win');
assert.ok(/skipped 2 malformed line\(s\)/.test(captured),
`expected aggregated malformed-line breadcrumb on stderr, got: ${captured}`);
const secondResult = readSessionCost('S1');
assert.deepStrictEqual(secondResult, result);
const matches = captured.match(/skipped 2 malformed line\(s\)/g) || [];
assert.strictEqual(matches.length, 1,
`expected one aggregated malformed-line breadcrumb on stderr, got: ${captured}`);
} finally {
process.stderr.write = originalStderrWrite;
if (originalHome === undefined) delete process.env.HOME;