Fix loom on Claude Code 2.1.x: /loom:<role> namespace + BypassPermissions auto-accept#3348
Conversation
|
Thanks for putting this together, @jperla — and for the careful write-up in #3345 with the verified reproduction. Sorry about the timing collision here. Status: partially superseded, partially still wantedHalf 1:
|
Two changes needed to make loom work against Claude Code 2.1.133: 1. Use /loom:<role> namespaced slash command form. Claude Code 2.1+ only auto-discovers nested .claude/commands/loom/<role>.md via the /loom: namespace prefix; the bare /<role> form fails with "Unknown command". Applies to agent_spawn.py role_cmd construction and agent_monitor.py stuck-prompt recovery. 2. Auto-accept the BypassPermissions warning on every spawn. On this Claude Code build the acknowledgement is not persisted to CLAUDE_CONFIG_DIR — every fresh process shows the modal, default selection is "1. No, exit", so an unattended Enter would EXIT the spawn immediately. We schedule a delayed Down+Enter at +8s via a backgrounded subprocess so the spawned agent silently accepts "2. Yes, I accept" and proceeds. Tested live against QuillUI for both shepherd and support-role spawns. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses Judge feedback on PR rjwalters#3348. The original auto-accept logic used `subprocess.Popen(["bash", "-c", "sleep 8 && tmux send-keys ..."])` which: - Raced a fixed 8s timer against modal render — too short on slow boxes (misses the prompt entirely), wasteful on fast boxes. - Orphaned a detached bash process per spawn. - Reimported subprocess as `_subprocess` despite the module-level import. Replace with a poll-loop helper that uses the module's existing `_tmux()` wrapper: - `_bypass_prompt_visible(session_name)` — runs `tmux capture-pane` and matches against BYPASS_PROMPT_MARKERS ("Bypass Permissions mode" plus the `--dangerously-skip-permissions` flag text as a fallback marker, since future Claude Code builds may rename the warning). - `_auto_accept_bypass_prompt(session_name, ...)` — polls up to DEFAULT_BYPASS_POLL_TIMEOUT (15s) with DEFAULT_BYPASS_POLL_INTERVAL (1s). On detection, sends `Down Enter` via `_tmux("send-keys", ...)` to select "2. Yes, I accept". Accepts an injectable `sleep_fn` for deterministic testing. - Gated behind `LOOM_AUTO_ACCEPT_BYPASS`, default "1" (enabled). Set to "0" to disable on builds where `--dangerously-skip-permissions` already suppresses the modal. The post-spawn call in `spawn_agent` now reads as a synchronous helper invocation rather than the brittle subprocess.Popen pattern. Tests (`TestAutoAcceptBypassPrompt`, 6 cases): - sends Down+Enter when modal is detected on first poll - polls until modal appears across multiple iterations - honours the timeout budget when modal never appears - LOOM_AUTO_ACCEPT_BYPASS=0 disables the path entirely - alternate marker (`--dangerously-skip-permissions`) also detected - non-zero `capture-pane` returncode treated as "not yet" All 6 new tests pass; the 69 pre-existing `test_agent_spawn.py` tests remain at their main-branch baseline (one pre-existing unrelated TestValidateRole failure was present before this PR). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
b6f2b21 to
f7de604
Compare
|
Thanks for the patch @jperla — sorry for the timing collision. Here's what happened and what's now ready for re-review: RebaseThe branch was rebased onto current Your BypassPermissions auto-accept half survived the rebase intact as commit Doctor fixes (commit f7de604)The Judge raised 6 concerns on the auto-accept logic. Addressed in a single fixup commit on top of yours:
Re-reviewLabels updated to (doctor pass by @rjwalters per maintainer takeover) |
|
LGTM after Doctor takeover. Re-reviewed final state of the PR (commits Scope verification
The 6 Judge concerns, verified against the implementation
Additional checks
CI Approving. |
Captures the 8 PRs that landed since v0.8.1: Tauri removal (#3353), Claude Code 2.1+ compatibility hardening (#3352, #3356, #3348), post-build quality gate (#3355), host-sleep readiness check (#3357), and complementary builder/judge perf-guidance docs (#3351, #3354). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When /loom:doctor (or any subagent that mutates a PR branch) ran on an external-fork PR — i.e., a PR whose branch did not match `feature/issue-<N>` — it had no dedicated worktree path to use and fell through to `gh pr checkout <N>` in the orchestrator's main worktree. That switched the orchestrator's HEAD off main and could leave behind untracked files from the PR (concrete incident: v0.9.0 session, jperla's #3348, src-tauri/ orphans). This change: - Adds `.loom/scripts/pr-worktree.sh` — creates an isolated worktree at `.loom/worktrees/pr-<PR_NUMBER>/` with a `.loom-managed` sentinel, then runs `gh pr checkout` from INSIDE the worktree so the PR branch never touches the orchestrator's HEAD. - Updates `defaults/.claude/commands/loom/doctor.md` to document the branch-name heuristic (`^feature/issue-([0-9]+)$` -> issue worktree; anything else -> pr-worktree) and updates every `gh pr checkout` site to route through the right helper before any mutation. - Tightens the branch-to-issue-number regex in `merge-pr.sh` from the loose trailing-digit heuristic to the strict `^feature/issue-([0-9]+)$` pattern, so branches like `release-1` or `fix-bug-42` are correctly classified as PR-style (not issue-style). - Extends `merge-pr.sh` cleanup to recognize and remove `.loom/worktrees/pr-<N>/` in addition to `.loom/worktrees/issue-<N>/`. - Adds regression test `test-pr-worktree-isolation.sh` covering branch classification, sentinel placement, and the invariant that the orchestrator's HEAD is unchanged after a doctor pass on `fix/foo-bar`. `.claude/commands/` is a symlink to `defaults/.claude/commands/` in this repo so the doctor.md update propagates automatically. Closes #3358 Co-authored-by: Loom Worker <loom-reviewer@users.noreply.github.comecho> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes to make loom spawn working agents on Claude Code 2.1.133. Tested live against a real project (QuillUI) for both shepherd and support-role spawns.
1. Use the
/loom:<role>namespaced slash-command form (fixes #3345)After #3176 moved commands to
.claude/commands/loom/<role>.md, Claude Code 2.1+ only auto-discovers them via the/loom:<role>namespace prefix. The bare/<role>form fails withUnknown command: /<role>, so every freshly spawned agent stalls at startup.agent_spawn.py—role_cmdconstruction now emits/loom:{role}agent_monitor.py— stuck-prompt recovery uses the same namespaced formThis is the clean, uncontroversial half — it's just matching the resolver behavior introduced alongside #3176. See #3345 for the full repro.
2. Auto-accept the BypassPermissions warning on spawn
On this Claude Code build the BypassPermissions acknowledgement is not persisted to
CLAUDE_CONFIG_DIR, so every fresh process shows the modal. The default selection is "1. No, exit", which means an unattended Enter exits the spawn immediately. We schedule a delayedDown+Enterat +8s via a backgrounded subprocess so the agent silently selects "2. Yes, I accept" and proceeds.This half is a pragmatic workaround, not a polished fix — the +8s timing is a heuristic and the keystroke-injection is ugly. I'm opening it for visibility; happy to drop it from this PR if you'd prefer to solve the persisted-acknowledgement problem a different way, and keep this PR to the namespace fix alone.
🤖 Generated with Claude Code