Bug-fix contract's first customer: the merge-robot's silent no-op (green runs, satisfied merge conditions, zero merges, no error) is discharged red-first.
Root-cause findings: (1) merge refusals were print-and-continue (exit 0) — the failure mode henxels' own doctrine calls "a silent pass is a defect"; (2) no token-identity verification — a scoped-actions-token 403 was indistinguishable from "nothing to do"; (3) inline untestable script.
Fix: logic extracted to scripts/merge_robot.py (testable); identity verified FIRST (GET /user, logged); merge refusals (403 scope, unexpected 405, URLError) FAIL THE JOB with full error body; "behind the base branch" 405 correctly a skip, not a crash; every PR produces a decision line; workflow_dispatch added for on-demand runs.
Frozen tests (9): identity-first ordering, 403/405 refusals raise + main exits 1, success merges, pending/conflict/behind skip without POST, exit 0 path. Test doubles simulate the SERVER contract (mergeable flag included) — the circularity the spec-gate warned about, avoided.
Workflow-contract tests assert the wiring; the discharge record for this fix lives in this PR body (Discharge: robot-noop -> tests/test_merge_robot.py).
Verification
Suite 613+84 green (604 baseline + 9); ratchet PASS; ruff clean; henxels exit 0; workflow YAML parses.
Live acceptance: after merge, the next robot run against the queued green PRs (#205/#206/#207/#208/#209/#210) must show identity + per-PR decisions, and any refusal must fail the run visibly.
## Summary
- Bug-fix contract's first customer: the merge-robot's silent no-op (green runs, satisfied merge conditions, zero merges, no error) is discharged red-first.
- Root-cause findings: (1) merge refusals were print-and-continue (exit 0) — the failure mode henxels' own doctrine calls "a silent pass is a defect"; (2) no token-identity verification — a scoped-actions-token 403 was indistinguishable from "nothing to do"; (3) inline untestable script.
- Fix: logic extracted to `scripts/merge_robot.py` (testable); identity verified FIRST (GET /user, logged); merge refusals (403 scope, unexpected 405, URLError) FAIL THE JOB with full error body; "behind the base branch" 405 correctly a skip, not a crash; every PR produces a decision line; `workflow_dispatch` added for on-demand runs.
- Frozen tests (9): identity-first ordering, 403/405 refusals raise + main exits 1, success merges, pending/conflict/behind skip without POST, exit 0 path. Test doubles simulate the SERVER contract (mergeable flag included) — the circularity the spec-gate warned about, avoided.
- Workflow-contract tests assert the wiring; the discharge record for this fix lives in this PR body (Discharge: robot-noop -> tests/test_merge_robot.py).
## Verification
- Suite 613+84 green (604 baseline + 9); ratchet PASS; ruff clean; henxels exit 0; workflow YAML parses.
- Live acceptance: after merge, the next robot run against the queued green PRs (#205/#206/#207/#208/#209/#210) must show identity + per-PR decisions, and any refusal must fail the run visibly.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
scripts/merge_robot.py(testable); identity verified FIRST (GET /user, logged); merge refusals (403 scope, unexpected 405, URLError) FAIL THE JOB with full error body; "behind the base branch" 405 correctly a skip, not a crash; every PR produces a decision line;workflow_dispatchadded for on-demand runs.Verification