fix(hooks): skip read-guard advisory on Claude Code runtime (#2001)
* fix(hooks): skip read-guard advisory on Claude Code runtime (#1984) Claude Code natively enforces read-before-edit at the runtime level, so the gsd-read-guard.js advisory is redundant — it wastes ~80 tokens per Write/Edit call and clutters tool flow with system-reminder noise. Add early exit when CLAUDE_SESSION_ID is set (standard Claude Code session env var). Non-Claude runtimes (OpenCode, Gemini, etc.) that lack native read-before-edit enforcement continue to receive the advisory as before. Closes #1984 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(hooks): sanitize runHook env to prevent test failures in Claude Code The runHook() test helper now blanks CLAUDE_SESSION_ID so positive-path tests pass even when the test suite runs inside a Claude Code session. The new skip test passes the env var explicitly via envOverrides. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,11 @@ process.stdin.on('end', () => {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Claude Code natively enforces read-before-edit — skip the advisory (#1984)
|
||||
if (process.env.CLAUDE_SESSION_ID) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const filePath = data.tool_input?.file_path || '';
|
||||
if (!filePath) {
|
||||
process.exit(0);
|
||||
|
||||
@@ -26,14 +26,17 @@ const HOOK_PATH = path.join(__dirname, '..', 'hooks', 'gsd-read-guard.js');
|
||||
* Run the read guard hook with a given tool input payload.
|
||||
* Returns { exitCode, stdout, stderr }.
|
||||
*/
|
||||
function runHook(payload) {
|
||||
function runHook(payload, envOverrides = {}) {
|
||||
const input = JSON.stringify(payload);
|
||||
// Sanitize CLAUDE_SESSION_ID so positive-path tests work inside Claude Code sessions
|
||||
const env = { ...process.env, CLAUDE_SESSION_ID: '', ...envOverrides };
|
||||
try {
|
||||
const stdout = execFileSync(process.execPath, [HOOK_PATH], {
|
||||
input,
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env,
|
||||
});
|
||||
return { exitCode: 0, stdout: stdout.trim(), stderr: '' };
|
||||
} catch (err) {
|
||||
@@ -221,4 +224,19 @@ describe('gsd-read-guard hook', () => {
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.stdout, '');
|
||||
});
|
||||
|
||||
// ─── Claude Code runtime skip (#1984) ─────────────────────────────────
|
||||
|
||||
test('skips advisory on Claude Code runtime (CLAUDE_SESSION_ID set)', () => {
|
||||
const filePath = path.join(tmpDir, 'existing.js');
|
||||
fs.writeFileSync(filePath, 'const x = 1;\n');
|
||||
|
||||
const result = runHook(
|
||||
{ tool_name: 'Edit', tool_input: { file_path: filePath, old_string: 'const x = 1;', new_string: 'const x = 2;' } },
|
||||
{ CLAUDE_SESSION_ID: 'test-session-123' }
|
||||
);
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.stdout, '', 'should produce no output on Claude Code');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user