|
1 | 1 | #!/usr/bin/env node |
2 | | -// Claude Code PreToolUse guard: blocks actions the corpus forbids outright, before |
3 | | -// they run. Exit 2 blocks the tool call and feeds stderr back to the agent. |
4 | | -// Registered per-repo — see bootstrap.md. Two forbidden actions today: |
5 | | -// 1. Bash pushes that delete a remote branch (the delete-push fails in this |
6 | | -// environment, so it can never succeed). |
7 | | -// 2. Scheduling a deferred self-check-in to confirm CI / babysit a PR |
8 | | -// (send_later / ScheduleWakeup / create_trigger): a webhook that won't push |
9 | | -// CI-success is a reason to *query* the status directly, not to poll on a |
10 | | -// re-arming cadence. |
| 2 | +// Claude Code PreToolUse guard: blocks Bash commands that delete a remote branch |
| 3 | +// (the delete-push fails in this environment, so it can never succeed). |
| 4 | +// Exit 2 blocks the tool call and feeds stderr back to the agent. Registered |
| 5 | +// per-repo — see bootstrap.md. |
11 | 6 | let input = ''; |
12 | 7 | process.stdin.on('data', (d) => { input += d; }); |
13 | 8 | process.stdin.on('end', () => { |
14 | 9 | let payload = {}; |
15 | 10 | try { payload = JSON.parse(input); } catch { /* no payload → allow */ } |
16 | | - const name = payload.tool_name ?? ''; |
17 | | - |
18 | | - if (name === 'Bash') { |
19 | | - const cmd = payload.tool_input?.command ?? ''; |
20 | | - const deletesRemoteBranch = |
21 | | - /\bgit\s+push\b[^\n;&]*\s(--delete|-d)\s/.test(cmd) || |
22 | | - /\bgit\s+push\b[^\n;&]*\s\S+\s+:\S/.test(cmd); |
23 | | - if (deletesRemoteBranch) { |
24 | | - process.stderr.write( |
25 | | - 'Blocked: never delete a remote branch — a current environment bug makes the delete-push fail, so it cannot succeed. Leave the branch; it can be deleted from the GitHub UI if needed.' |
26 | | - ); |
27 | | - process.exit(2); |
28 | | - } |
29 | | - process.exit(0); |
| 11 | + if (payload.tool_name !== 'Bash') process.exit(0); |
| 12 | + const cmd = payload.tool_input?.command ?? ''; |
| 13 | + const deletesRemoteBranch = |
| 14 | + /\bgit\s+push\b[^\n;&]*\s(--delete|-d)\s/.test(cmd) || |
| 15 | + /\bgit\s+push\b[^\n;&]*\s\S+\s+:\S/.test(cmd); |
| 16 | + if (deletesRemoteBranch) { |
| 17 | + process.stderr.write( |
| 18 | + 'Blocked: never delete a remote branch — a current environment bug makes the delete-push fail, so it cannot succeed. Leave the branch; it can be deleted from the GitHub UI if needed.' |
| 19 | + ); |
| 20 | + process.exit(2); |
30 | 21 | } |
31 | | - |
32 | | - // send_later / create_trigger arrive MCP-namespaced (mcp__<server>__send_later); |
33 | | - // ScheduleWakeup is a built-in. Match by suffix so server casing doesn't matter. |
34 | | - const schedulesWakeup = |
35 | | - name === 'ScheduleWakeup' || name.endsWith('send_later') || name.endsWith('create_trigger'); |
36 | | - if (schedulesWakeup) { |
37 | | - const text = payload.tool_input?.message ?? payload.tool_input?.prompt ?? ''; |
38 | | - const isSelfCheckIn = |
39 | | - /\bself[-\s]?check[-\s]?in\b/i.test(text) || |
40 | | - /\bre-?arm\b/i.test(text) || |
41 | | - /\bconfirm\b[^.]*\bgreen\b/i.test(text); |
42 | | - if (isSelfCheckIn) { |
43 | | - process.stderr.write( |
44 | | - "Blocked: don't schedule a deferred self-check-in to confirm CI or babysit a PR. A webhook not delivering CI-success is a reason to *query*, not to poll on a re-arming cadence. Ask the platform directly for the check-run / workflow-run status (e.g. pull_request_read get_check_runs) on a short backoff bounded to the run's own duration, until it settles: green → report and stop, red → act. Don't re-arm — a stale-but-open PR awaiting human review is a safe state to end your turn on." |
45 | | - ); |
46 | | - process.exit(2); |
47 | | - } |
48 | | - } |
49 | | - |
50 | 22 | process.exit(0); |
51 | 23 | }); |
0 commit comments