mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-21 03:40:05 +08:00
fix(hooks): use unique tmp suffix in writeWarnState (ecc-context-monitor)
Mirror the previous commit's `writeBridgeAtomic` fix on the
companion `writeWarnState` in `ecc-context-monitor.js`. Same shape:
fixed `${target}.tmp` → `${target}.${process.pid}.${randomNonce}.tmp`,
plus best-effort cleanup of the tmp file on `renameSync` failure
(throws original error after cleanup).
`writeWarnState` debounces the context-monitor's threshold alarms
(`COST_NOTICE_USD`, `COST_WARNING_USD`, `COST_CRITICAL_USD`, plus the
context-remaining and loop-detection ones). Without unique suffixes,
two PostToolUse subprocesses racing on the warn-state file produce
either a corrupted JSON debounce-state on disk or an ENOENT throw
that the hook catches and swallows — either way the next warn-state
read returns the default `{callsSinceWarn: 0, lastSeverity: null}`
and the threshold alarms re-fire or stop firing erratically. Users
see warning messages flicker or vanish; debounce no longer works.
Three call sites in this repo now share the same atomic-write
contract:
- `writeBridgeAtomic` (scripts/lib/session-bridge.js) — primary
- `writeCostWarningIfChanged` (scripts/hooks/ecc-metrics-bridge.js) — cost cache
- `writeWarnState` (this file) — debounce state
`yarn lint` clean. Regression test covering both `writeBridgeAtomic`
and `writeWarnState` under concurrent load lands in the next commit.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
@@ -61,15 +62,30 @@ function readWarnState(sessionId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write debounce state.
|
||||
* Write debounce state atomically (unique-suffix tmp then rename).
|
||||
*
|
||||
* The tmp path includes `process.pid` plus a random nonce so concurrent
|
||||
* PostToolUse subprocesses writing to the same session's warn-state
|
||||
* file do not clobber each other's tmp mid-write. Without the unique
|
||||
* suffix, two writers race over a shared `${target}.tmp` and produce
|
||||
* either a corrupted payload or an ENOENT throw on the second rename.
|
||||
*
|
||||
* Same pattern as `writeBridgeAtomic` in `scripts/lib/session-bridge.js`
|
||||
* and `writeCostWarningIfChanged` in `scripts/hooks/ecc-metrics-bridge.js`.
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @param {object} state
|
||||
*/
|
||||
function writeWarnState(sessionId, state) {
|
||||
const target = getWarnPath(sessionId);
|
||||
const tmp = `${target}.tmp`;
|
||||
const tmp = `${target}.${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
||||
fs.writeFileSync(tmp, JSON.stringify(state), 'utf8');
|
||||
fs.renameSync(tmp, target);
|
||||
try {
|
||||
fs.renameSync(tmp, target);
|
||||
} catch (err) {
|
||||
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user