Skip to content

Commit 306d555

Browse files
missingbulbclaude
andauthored
Roll back the PR-self-check-in guard and draft-merge workaround (#152)
Two Claude-side settings that drove PR churn — draft-by-default PRs and "Autofix pull requests" (auto-subscribe + hourly self-check-in) — have been turned off at the source. The guards the corpus grew to smooth those over now guard a phantom, so remove them: - checks/pretooluse-guard.mjs: drop the deferred-self-check-in branch (send_later / ScheduleWakeup / create_trigger). The remote-branch-delete guard stays. - .claude/settings.json + bootstrap.md: PreToolUse matcher back to `Bash`. - checks/test/guard.test.mjs: drop the self-check-in fixtures. - skills/merge-to-main: drop the "un-draft before merging" step — PRs are no longer opened as draft. Kept from the lessons pass: the get_status-misses-Actions-CI gotcha in git-github-advanced — reading CI via check runs is setting-independent. Closes #154. Refs #151. Claude-Session: https://claude.ai/code/session_01HzjetxNGYuN5wwF1vRdkA8 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 14e7beb commit 306d555

5 files changed

Lines changed: 21 additions & 71 deletions

File tree

.claude/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
],
1717
"PreToolUse": [
1818
{
19-
"matcher": "Bash|send_later|create_trigger|ScheduleWakeup",
19+
"matcher": "Bash",
2020
"hooks": [
2121
{
2222
"type": "command",

bootstrap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,10 @@ The corpus's enforceable rules run as deterministic checks — usage, configurat
148148

149149
No ordering constraint: Stop fires at end of turn, long after the SessionStart sync (Method B) or submodule update (Method A) has populated `.claudinite/`.
150150

151-
**2.** Register the PreToolUse guard alongside it (same file; skip if present). It deterministically blocks actions the corpus forbids outright — currently remote-branch-delete pushes (which fail in this environment) and deferred PR self-check-in scheduling (a `send_later`/`ScheduleWakeup`/`create_trigger` call to "confirm CI goes green" or re-arm a PR watch — query the check-run status directly instead):
151+
**2.** Register the PreToolUse guard alongside it (same file; skip if present). It deterministically blocks commands the corpus forbids outright — currently remote-branch-delete pushes, which fail in this environment:
152152

153153
```json
154-
{ "hooks": { "PreToolUse": [ { "matcher": "Bash|send_later|create_trigger|ScheduleWakeup", "hooks": [
154+
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [
155155
{ "type": "command", "command": "node $CLAUDE_PROJECT_DIR/.claudinite/checks/pretooluse-guard.mjs" }
156156
] } ] } }
157157
```

checks/pretooluse-guard.mjs

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,23 @@
11
#!/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.
116
let input = '';
127
process.stdin.on('data', (d) => { input += d; });
138
process.stdin.on('end', () => {
149
let payload = {};
1510
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);
3021
}
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-
5022
process.exit(0);
5123
});

checks/test/guard.test.mjs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,3 @@ test('allows ordinary pushes and non-Bash tools', () => {
3434
assert.equal(runGuard(payload).status, 0, JSON.stringify(payload));
3535
}
3636
});
37-
38-
const CHECKIN =
39-
'Self check-in for PR #43. Re-check the PR state, CI status, mergeability, and any new review comments. If nothing changed, re-arm the next check-in silently ~1h out. Stop once the PR is merged or closed.';
40-
41-
test('blocks a deferred PR self-check-in across scheduling tools', () => {
42-
for (const payload of [
43-
{ tool_name: 'mcp__claude-code-remote__send_later', tool_input: { delay_minutes: 60, message: CHECKIN } },
44-
{ tool_name: 'ScheduleWakeup', tool_input: { prompt: CHECKIN, delaySeconds: 3600 } },
45-
{ tool_name: 'mcp__claude-code-remote__create_trigger', tool_input: { prompt: 'Schedule a self check-in to re-check PR #7 hourly.' } },
46-
{ tool_name: 'ScheduleWakeup', tool_input: { prompt: 'Wait for CI, then confirm it goes green and report back.', delaySeconds: 600 } },
47-
]) {
48-
const r = runGuard(payload);
49-
assert.equal(r.status, 2, JSON.stringify(payload));
50-
assert.match(r.stderr, /query|check-run|re-arm|directly/i);
51-
}
52-
});
53-
54-
test('allows legit scheduled reminders and unrelated tools', () => {
55-
for (const payload of [
56-
{ tool_name: 'mcp__claude-code-remote__send_later', tool_input: { delay_minutes: 30, message: 'Remind me to review the design doc with Bob.' } },
57-
{ tool_name: 'ScheduleWakeup', tool_input: { prompt: 'Resume the migration once the data export finishes.', delaySeconds: 900 } },
58-
{ tool_name: 'Edit', tool_input: {} },
59-
]) {
60-
assert.equal(runGuard(payload).status, 0, JSON.stringify(payload));
61-
}
62-
});

skills/git-github-advanced/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ When the escalation is itself a separate reusable workflow invoked via `workflow
8787

8888
On `pull_request`, `actions/checkout` checks out the synthetic `refs/pull/N/merge` commit by default — an ephemeral merge of the PR head into the base — so `HEAD` is itself a merge commit, not the branch tip. Any job that reasons about commit *shape* or history (merge commits, commit count, `--first-parent`, commit messages) reads that synthetic merge and misfires: a "no merge commits" check false-positives even on a linear branch, since `HEAD` is a merge. Check out the head SHA (`ref: ${{ github.event.pull_request.head.sha || github.sha }}`) so CI evaluates the same branch tip a local run does.
8989

90+
## To read a PR's CI result, look at its check runs — `get_status` misses Actions
91+
92+
GitHub **Actions** reports results as **check runs**, not the legacy **commit statuses**, so `pull_request_read` `method: get_status` returns `state: pending`, `total_count: 0` for a PR whose Actions CI has already **passed** — reading falsely as "no CI / not started," which can skip a real gate or trigger an endless wait. Gate on CI by reading the **check runs for the PR head SHA** (`get_check_run`, or the workflow run for that SHA) instead. Target the head SHA directly rather than `actions_list`-ing the repo's runs — that returns every run, its output is huge, and a specific check-run/SHA query is both correct and cheap.
93+
9094
## Mark large committed fixtures `linguist-vendored` to fix language stats
9195

9296
Large committed fixture files (full-page HTML, generated data dumps) can dwarf actual source by byte count and cause GitHub to mislabel the repo's primary language. Add a `.gitattributes` entry for each such path (e.g. `test/fixtures/*.html linguist-vendored`) to tell Linguist to ignore it; apply the same annotation whenever you add another large generated or fixture file.

0 commit comments

Comments
 (0)