constallow=newSet(['ls','cat','grep','rg','git','npm','node','python','python3','curl','jq','head','tail']);constfirst=(cmd.trim().split(/\s+/)[0]||"").split('/').pop();if(!allow.has(first)){console.error(`Not on allowlist: ${first}`);process.exit(2);}
白名单赢在黑名单输的地方:你不可能意外放过新东西。
4. block-git-push——不允许偷推
我从来不希望 Claude 不打招呼就 push。PreToolUse 接 Bash:
1
2
3
4
if(/^\s*git\s+push\b/.test(cmd)||/git.*push.*--force/.test(cmd)){console.error("Blocked: git push must be human-initiated.");process.exit(2);}
if(/\/(src|lib)\/.*\.(ts|js)$/.test(path)){try{require('child_process').execSync('npm run -s test:related -- '+path,{stdio:'inherit'});}catch{console.error("Tests failed after edit");process.exit(1);}}
Exit code 1 把失败上抛给模型,它看到测试输出就会再试一次。这一个 hook 是唯一让 Claude 在我仓库里写出越来越好代码的那个。
constfs=require('fs');constseen=JSON.parse(fs.existsSync('.claude/seen.json')?fs.readFileSync('.claude/seen.json'):"{}");if(t.tool_name==='Read'){seen[path]=Date.now();fs.writeFileSync('.claude/seen.json',JSON.stringify(seen));process.exit(0);}if(!seen[path]){console.error(`Blocked: ${path} was not Read in this session.`);process.exit(2);}
强制模型在改文件前先读它。专治那种"模型按它的先验改而不是按文件当前状态改"的隐蔽 bug。
10. work-hours-only——人道的边界
PreToolUse 接 Bash:
1
2
3
4
5
consth=newDate().getHours();if(h<9||h>=22){console.error("Outside work hours. Refuse.");process.exit(2);}