diff --git a/scripts/hooks/gateguard-fact-force.js b/scripts/hooks/gateguard-fact-force.js index 4bbde83e..c200b83f 100644 --- a/scripts/hooks/gateguard-fact-force.js +++ b/scripts/hooks/gateguard-fact-force.js @@ -59,9 +59,12 @@ function loadState() { function saveState(state) { try { state.last_active = Date.now(); - // Prune checked list if it exceeds the cap + // Prune checked list if it exceeds the cap. + // Preserve session keys (__prefixed) so gates like __bash_session__ don't re-fire. if (state.checked.length > MAX_CHECKED_ENTRIES) { - state.checked = state.checked.slice(-MAX_CHECKED_ENTRIES); + const sessionKeys = state.checked.filter(k => k.startsWith('__')); + const fileKeys = state.checked.filter(k => !k.startsWith('__')); + state.checked = [...sessionKeys, ...fileKeys.slice(-(MAX_CHECKED_ENTRIES - sessionKeys.length))]; } fs.mkdirSync(STATE_DIR, { recursive: true }); // Atomic write: temp file + rename prevents partial reads diff --git a/tests/hooks/gateguard-fact-force.test.js b/tests/hooks/gateguard-fact-force.test.js index 9ff3e432..98b1c909 100644 --- a/tests/hooks/gateguard-fact-force.test.js +++ b/tests/hooks/gateguard-fact-force.test.js @@ -9,9 +9,9 @@ const { spawnSync } = require('child_process'); const runner = path.join(__dirname, '..', '..', 'scripts', 'hooks', 'run-with-flags.js'); const stateDir = process.env.GATEGUARD_STATE_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.gateguard-test-' + process.pid); -// State files are per-session. In tests, falls back to pid-based name. -const sessionId = process.env.CLAUDE_SESSION_ID || process.env.ECC_SESSION_ID || `pid-${process.ppid || process.pid}`; -const stateFile = path.join(stateDir, `state-${sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`); +// Use a fixed session ID so test process and spawned hook process share the same state file +const TEST_SESSION_ID = 'gateguard-test-session'; +const stateFile = path.join(stateDir, `state-${TEST_SESSION_ID}.json`); function test(name, fn) { try { @@ -60,6 +60,7 @@ function runHook(input, env = {}) { ...process.env, ECC_HOOK_PROFILE: 'standard', GATEGUARD_STATE_DIR: stateDir, + CLAUDE_SESSION_ID: TEST_SESSION_ID, ...env }, timeout: 15000, @@ -87,6 +88,7 @@ function runBashHook(input, env = {}) { ...process.env, ECC_HOOK_PROFILE: 'standard', GATEGUARD_STATE_DIR: stateDir, + CLAUDE_SESSION_ID: TEST_SESSION_ID, ...env }, timeout: 15000,