[failproofai-487] Skip require-*-before-stop gates in Claude Code plan mode#488
Conversation
…n mode The five require-*-before-stop workflow gates fired on every Stop event without checking the session's permission mode. In Claude Code plan mode (permission_mode: "plan") the agent makes no commits/pushes/PRs by design, so the gates demanded actions plan mode forbids — e.g. "git push" with nothing to push — blocking the agent from finishing its turn. permission_mode is already resolved onto ctx.session.permissionMode (resolve-permission-mode.ts -> handler.ts) but was only consumed for activity logging and the dashboard badge. Add an isPlanMode(ctx) guard as the first statement of each of the five policies (commit/push/pr/ no-conflicts/ci-green) so they skip in plan mode and stay fully active in default sessions; custom Stop policies are untouched. Adds a plan-mode regression test to each policy suite asserting allow + that no git/gh subprocess runs (guard short-circuits before any exec). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Gn9p2tji5JjqAWAVg23ds
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughThe stop policies detect Claude Code plan mode and allow completion without checking commits, pushes, pull requests, conflicts, or CI status. Regression tests verify no subprocesses run, and the changelog documents the ChangesPlan-mode workflow gating
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The PR landed as #488 (the predicted #487 was taken in between). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Gn9p2tji5JjqAWAVg23ds
|
🔍 Automated code review started — analyzing [{"additions":5,"deletions":0,"path":"CHANGELOG.md"},{"additions":59,"deletions":0,"path":"tests/hooks/builtin-policies.test.ts"},{"additions":16,"deletions":0,"path":"src/hooks/builtin-policies.ts"}] files, +80/-0... ⏱️ This may take a few minutes. Results will be posted here when complete. |
| * reports "plan" today; every other CLI resolves to "default". | ||
| */ | ||
| function isPlanMode(ctx: PolicyContext): boolean { | ||
| return ctx.session?.permissionMode === "plan"; |
There was a problem hiding this comment.
🔵 INFO (non-blocking): isPlanMode gates on the session's current permission mode at Stop time, but the five checks it skips (commit / push / PR / conflicts / CI) inspect the git working-tree and remote state, which can carry work from an earlier default-mode turn.
So if the tree has uncommitted/unpushed changes from a prior turn and the current turn ends in plan mode (e.g. the user toggled to plan mode before stopping), all five gates are skipped and enforcement is deferred to the next non-plan Stop. Nothing is lost — the work stays in the tree and the next default-mode Stop re-checks it — and this matches the PR's intent (plan mode = "don't bug me"), so no change is required.
Flagging only so it's a conscious call: the "…no changes made…" reason string is an approximation — it means this turn made none, not that the tree is clean.
🔍 Automated Code Review📋 Executive SummaryThis PR fixes a real contradiction-by-construction bug: the five 📊 Change Architecturegraph TD
A[Stop event · Claude Code] --> B["handler.ts<br/>resolvePermissionMode()"]
B -->|"session.permissionMode"| C["isPlanMode(ctx)<br/>NEW helper"]
C -->|"=== 'plan'"| D["allow() — skip gate<br/>NEW guard ×5"]
C -->|"!== 'plan'"| E["Run git/gh checks<br/>(unchanged)"]
D --> F[Agent stops cleanly]
E --> G{clean?}
G -->|yes| F
G -->|no| H["deny — MANDATORY ACTION"]
style C fill:#90EE90
style D fill:#90EE90
Legend: 🟢 New · 🔵 Modified · 🟡 Breaking-change risk 🔴 Breaking Changes✅ No breaking changes detected. The change is purely additive — a new early-return guard that only triggers when
|
hermes-exosphere
left a comment
There was a problem hiding this comment.
✅ Automated review: APPROVED.
Correct, minimal, additive (+80/−0) fix for a genuine plan-mode false positive in the five require-*-before-stop gates. Verified locally against 94c3fbd:
- 424 unit tests pass (incl. 5 new plan-mode regression tests — one per policy, each asserting no subprocess runs)
tsc --noEmitclean ·eslintclean · version-consistency OK · all CI checks green
No breaking changes, no logical errors. One non-blocking INFO note left inline re: deferred enforcement of pre-existing uncommitted work — acceptable and intentional. 🟢
Problem
At the end of a plan-mode turn in Claude Code, the
require-push-before-stopgate fired:This is a contradiction by construction: plan mode is research-and-plan-only — the agent makes no commits, pushes, or PRs by design — yet the gate demanded a
git pushbefore it could stop, blocking the agent from finishing its turn.Root cause
The five
require-*-before-stopbuiltins fire on everyStopevent and never consult the session's permission mode, even though it is fully available:permission_mode: "plan"on the Stop payload during plan mode.ctx.session.permissionMode(resolve-permission-mode.ts:49→handler.ts:164) — but only uses it for activity logging and the dashboard badge, never for policy gating.So a plan-mode Stop runs the real git check, finds no pushed branch (correctly — plan mode changed nothing), and returns
exitCode: 2with the MANDATORY ACTION message.Fix
Add a one-line
isPlanMode(ctx)guard as the first statement of each of the five policies (commit/push/pr/no-conflicts/ci-green), matching each policy's existing early-skip idiom. They skip in plan mode and stay fully active in default sessions; custom Stop policies are untouched.permission_mode: "plan"is Claude-specific in practice (every other CLI resolves to"default"), which matches where the bug occurs.Tests
A plan-mode regression test in each policy's suite asserts the policy allows and that no
git/ghsubprocess runs (the guard short-circuits before anyexec). Full local run: 1912 unit + 298 e2e passing; lint (0 errors) +tsc --noEmitclean.🤖 Generated with Claude Code
https://claude.ai/code/session_012Gn9p2tji5JjqAWAVg23ds
Summary by CodeRabbit
0.0.13-beta.2changelog entry (dated 2026-07-10) to reflect the plan-mode Stop-gate fix.