Skip to content

fix: prevent stale gate state from blocking Gandalf (#56)#57

Merged
justinjdev merged 2 commits intomainfrom
worktree-56-gate-guard-hook-stale-state
Mar 9, 2026
Merged

fix: prevent stale gate state from blocking Gandalf (#56)#57
justinjdev merged 2 commits intomainfrom
worktree-56-gate-guard-hook-stale-state

Conversation

@justinjdev
Copy link
Copy Markdown
Owner

@justinjdev justinjdev commented Mar 9, 2026

Summary

  • Pre-flight CWD check in fellowship skill: Gandalf now aborts at startup if pwd contains .claude/worktrees, before spawning anything
  • Escape hatch in gate-guard hook: fellowship gate reject/approve and fellowship init are allowed through even when gate_pending is true — self-rescue without user manual intervention; shell chaining operators (&&, ||, ;, |) are rejected to prevent abuse; held state still blocks everything
  • New fellowship state clean-worktrees CLI command: scans all worktrees under .claude/worktrees/ and resets stale gate_pending/held flags in one shot

What's not fixed

  • Root cause 2 (hook applies to Gandalf) is mitigated by the pre-flight check
  • Root cause 4 (worktree directory collision) requires changes to EnterWorktree tool behavior outside our control

Test plan

  • TestGateGuard_AllowsFellowshipGateRejectWhenPending — escape hatch works for gate reject
  • TestGateGuard_AllowsFellowshipInitWhenPending — escape hatch works for init
  • TestGateGuard_BlocksChainedCommandsWithFellowshipEscape — chaining operators not bypassed
  • TestGateGuard_HeldBlocksFellowshipEscapeCommands — held state still blocks everything
  • go test ./... passes

Closes #56

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added fellowship state clean-worktrees command to reset GatePending and Held flags across all worktrees with per-worktree progress reporting
    • Permit specific Fellowship gate and init commands to execute during gate-pending conditions
  • Improvements

    • Added pre-flight validation to block command execution within quest worktree directories

Three fixes for the failure chain where stale quest-state.json blocks a
fresh fellowship session:

- fellowship skill: pre-flight CWD check aborts if Gandalf starts inside
  a .claude/worktrees/ path before spawning anything
- gate-guard hook: escape hatch allows `fellowship gate reject/approve`
  and `fellowship init` through even when gate_pending is true, giving
  a self-rescue path without requiring manual user intervention; shell
  chaining operators are rejected to prevent bypass abuse; held state
  still blocks everything (intentional lead-imposed pause)
- CLI: new `fellowship state clean-worktrees [--dir PATH]` command scans
  all worktrees and resets stale gate_pending/held flags in one shot

Closes #56

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2026

Warning

Rate limit exceeded

@justinjdev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6122ef70-1424-493d-9c08-164e0ad11761

📥 Commits

Reviewing files that changed from the base of the PR and between d5ee14f and d4ada42.

📒 Files selected for processing (3)
  • cli/cmd/fellowship/main.go
  • cli/internal/hooks/guard.go
  • cli/internal/hooks/guard_test.go
📝 Walkthrough

Walkthrough

This PR adds a fellowship state clean-worktrees subcommand to scan and clear stale GatePending and Held flags from quest-state.json files across all worktrees, modifies gate-guard to allow specific fellowship escape commands (gate reject/approve, init) when GatePending is true, adds test coverage for escape command behavior, and updates documentation with pre-flight validation guidance.

Changes

Cohort / File(s) Summary
Clean Worktrees Command
cli/cmd/fellowship/main.go
Introduces state clean-worktrees subcommand that scans worktree directories, locates quest-state.json files, clears GatePending/Held flags and HeldReason for stale state, reports per-worktree clears, and handles missing worktrees directory gracefully with appropriate error returns.
Gate Guard Escape Commands
cli/internal/hooks/guard.go
Adds isFellowshipEscapeCommand() helper to permit fellowship gate reject/approve and fellowship init commands when GatePending is true; rejects commands with shell chaining operators (&&, ||, ;, |); updates GateGuard to check escape command status before blocking on GatePending.
Gate Guard Tests
cli/internal/hooks/guard_test.go
Adds test cases verifying fellowship escape commands are allowed when GatePending is true, shell-chained escapes are blocked, escape commands are blocked when Held is true, and Held takes precedence over GatePending in messaging.
Startup Documentation
plugin/skills/fellowship/SKILL.md
Adds pre-flight verification step to detect and reject startup when running from inside a quest worktree (.claude/worktrees), includes context on configuration keys and default gating behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: preventing stale gate state from blocking Gandalf, which is the primary issue addressed across all code changes.
Linked Issues check ✅ Passed The PR addresses root causes 1, 2, and 3 from issue #56: pre-flight CWD check prevents Gandalf from starting in worktrees (1), gate-guard escape hatch allows fellowship commands when gate_pending (2), and clean-worktrees command resets stale state flags (3).
Out of Scope Changes check ✅ Passed All changes are scoped to addressing the three primary objectives: the SKILL.md pre-flight check, guard.go escape hatch logic, and the new clean-worktrees CLI command. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch worktree-56-gate-guard-hook-stale-state

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c2abe72-fde3-43a2-b44f-899df76ef4db

📥 Commits

Reviewing files that changed from the base of the PR and between a543067 and d5ee14f.

📒 Files selected for processing (4)
  • cli/cmd/fellowship/main.go
  • cli/internal/hooks/guard.go
  • cli/internal/hooks/guard_test.go
  • plugin/skills/fellowship/SKILL.md

Comment thread cli/cmd/fellowship/main.go
Comment thread cli/cmd/fellowship/main.go Outdated
Comment thread cli/internal/hooks/guard.go Outdated
@justinjdev
Copy link
Copy Markdown
Owner Author

Note — root cause 2 not fully addressed

The hook has no session identity in its input payload (only tool_name + tool_input). There's no way at the hook level to distinguish Gandalf from a quest runner. The state file records the quest runner's task_id, but the hook doesn't know the current session's task ID to compare against.

The clean fix would be: if Claude Code ever exposes session/task context in PreToolUse hook payloads, compare the current task ID against state.task_id and skip enforcement on mismatch. Worth a future feature request to Anthropic.

For now, the pre-flight CWD check (prevents the scenario) + escape hatch (recovery path) are the best we can do with the current hook API.

- clean-worktrees: distinguish ENOENT vs other Stat errors; warn on
  EACCES/EPERM instead of silently skipping
- clean-worktrees: use state.WithLock for atomic load→mutate→save and
  clear GateID alongside GatePending (consistent with other reset paths)
- isFellowshipEscapeCommand: tokenize command and match binary+subcommand
  exactly instead of substring; also reject <>, newlines, backtick, $()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: gate-guard hook blocks Gandalf when session starts inside a worktree with stale state

1 participant