fix: remove per-session gate, use permissionDecision deny for every read
The per-session FileReadGate was never requested and broke the cost savings loop — subsequent reads in the same session silently bypassed the timeline, hiding newly created observations. Now the timeline fires on every read that has observations, using the hook contract's permissionDecision: "deny" with the timeline as the reason (exit 0 + JSON) instead of exit code 2 + stderr. - Delete FileReadGate.ts entirely - Remove /api/file-context/gate endpoint from DataRoutes - Switch handler from exit code 2 to permissionDecision: "deny" - Restore permissionDecision fields to HookResult - Eliminate one HTTP round-trip per read (no gate check needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, workerHttpRequest } from '../../shared/worker-utils.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
|
||||
import { parseJsonArray } from '../../shared/timeline-formatting.js';
|
||||
import { statSync } from 'fs';
|
||||
import path from 'path';
|
||||
@@ -212,28 +211,17 @@ export const fileContextHandler: EventHandler = {
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
|
||||
// Check the gate: has this file's timeline been shown in this session?
|
||||
const gateResponse = await workerHttpRequest('/api/file-context/gate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId: input.sessionId, filePath: relativePath, cwd }),
|
||||
});
|
||||
|
||||
if (gateResponse.ok) {
|
||||
const gateData = await gateResponse.json() as { firstAttempt: boolean };
|
||||
|
||||
if (gateData.firstAttempt) {
|
||||
// BLOCK: Show timeline, Claude decides whether to re-read or use get_observations()
|
||||
const timeline = formatFileTimeline(dedupedObservations, filePath);
|
||||
return {
|
||||
exitCode: HOOK_EXIT_CODES.BLOCKING_ERROR,
|
||||
stderrMessage: timeline,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ALLOW: Second attempt or gate check failed — let the read proceed silently
|
||||
return { continue: true, suppressOutput: true };
|
||||
// Deny the read with the timeline as the reason — Claude sees the timeline
|
||||
// and decides: work from semantic priming, use get_observations(), or ask user to allow read
|
||||
const timeline = formatFileTimeline(dedupedObservations, filePath);
|
||||
return {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PreToolUse',
|
||||
additionalContext: '',
|
||||
permissionDecision: 'deny',
|
||||
permissionDecisionReason: timeline,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
logger.warn('HOOK', 'File context fetch error, skipping', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
|
||||
Reference in New Issue
Block a user