Skip to content

feat(hooks): worktree-collision guardrail (isolation:worktree guard + git-worktree-add warn rule)#46

Open
lapc506 wants to merge 2 commits into
mainfrom
andres/toolkit-worktree-collision-guardrail
Open

feat(hooks): worktree-collision guardrail (isolation:worktree guard + git-worktree-add warn rule)#46
lapc506 wants to merge 2 commits into
mainfrom
andres/toolkit-worktree-collision-guardrail

Conversation

@lapc506

@lapc506 lapc506 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Worktree-collision guardrail

Adds the two enforcement layers that prevent the sibling-recheckout collision — parallel isolation:worktree subagents commingling in one shared worktree, producing entangled commits recoverable only by forward-fix:

  • hooks/pre-task-worktree-isolation-guard.sh — PreToolUse on Task/Agent. Blocks (exit 2) a 2nd isolation:worktree spawn inside the provisioning window; an atomic mkdir lock handles same-message parallel spawns. Fail-open; honors CLAUDE_DISABLE_PLUGIN_HOOKS=1. Registered in hooks/hooks.json (new Task|Agent matcher).
  • git-worktree-add-discipline — warn rule in hooks/rules/rules.yaml (+5 tests), the upstream nudge toward "dedicated worktree + git -C".
  • references/agent-worktree-orchestration.md — the safe pattern + recovery doc.

Also: the warn-localhost-in-pr-body rule's example staging host was genericized to dev.example.com (public-toolkit agnostic).

rules.json regenerated in sync with rules.yaml. No version bump / CHANGELOG here — coordinate with the in-flight audit-engine release bump.

Created by Claude Code on behalf of @lapc506.

🤖 Generated with Claude Code

…orktree spawns + git-worktree-add warn rule

PreToolUse guard (Task/Agent) blocks a 2nd isolation:worktree spawn in the
provisioning window (atomic mkdir lock for same-message parallel spawns);
git-worktree-add-discipline warn rule nudges toward dedicated-worktree + git -C;
references/agent-worktree-orchestration.md documents the pattern + forward-fix
recovery. Genericized warn-localhost example host -> dev.example.com. No version
bump (coordinate with the audit-engine release).

Created by Claude Code on behalf of @lapc506.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Changes Requested

Changes requested — 1 blocker, 1 P3. Confidence: 1.00/5.00.

🔴 P1 — Blockers

  • hooks/pre-task-worktree-isolation-guard.sh:67 — 🔴 P1 (blocker) — A time-of-check to time-of-use (TOCTOU) race condition in lock reclamation can lead to a newly-created lock from a racing parallel process being deleted. This allows both processes to pass, defeating the primary parallel-spawn guardrail of the PR.

[pass 1]

🔵 P3 — Minor

  • hooks/pre-task-worktree-isolation-guard.sh:45 — 🔵 P3 (minor) — Validate that WINDOW_SECONDS is a positive integer to prevent runtime failures in bash arithmetic under set -u if a user provides an invalid custom MNM_WORKTREE_GUARD_WINDOW environment variable.

[pass 1]


Total findings: 1 security, 1 business context (2 total)

# window are treated as a racing batch. The SAFE multi-worktree pattern
# (pre-create + git -C, Rule 2) does NOT use isolation:worktree, so it never
# trips this guard.
WINDOW_SECONDS="${MNM_WORKTREE_GUARD_WINDOW:-90}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 P3 (minor) — Validate that WINDOW_SECONDS is a positive integer to prevent runtime failures in bash arithmetic under set -u if a user provides an invalid custom MNM_WORKTREE_GUARD_WINDOW environment variable.

[pass 1]

now="$(date +%s 2>/dev/null || echo 0)"
[[ "$now" =~ ^[0-9]+$ ]] || exit 0 # fail-open: no clock

# Reclaim a stale lock from a prior (already-provisioned) batch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 P1 (blocker) — A time-of-check to time-of-use (TOCTOU) race condition in lock reclamation can lead to a newly-created lock from a racing parallel process being deleted. This allows both processes to pass, defeating the primary parallel-spawn guardrail of the PR.

[pass 1]

@lapc506 lapc506 self-assigned this Jun 23, 2026
…(review #46)

P1: the rm -rf reclamation between the staleness check and mkdir could delete a
racing parallel process's freshly-created lock, letting both spawns pass. The
lock dir is now NEVER deleted — mkdir is the sole atomic gate and staleness is
decided by the timestamp (stale → allow + roll the window; fresh → block). P3:
fall back to the default window if MNM_WORKTREE_GUARD_WINDOW is not a positive
integer (avoids set -u arithmetic failure).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lapc506

lapc506 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

Both findings addressed in 2d26dd5:

  • P1 (TOCTOU, L67): removed the rm -rf stale-lock reclamation entirely. The lock dir is never deleted now — mkdir is the sole atomic gate, and staleness is decided by the timestamp (stale → allow + roll the window; fresh → block). No check-then-rm window, so a racing process's fresh lock can't be erased. (Residual stale-simultaneous case over-allows, never corrupts — documented inline; the load-bearing safety is the pre-create + git -C pattern.)
  • P3 (L45): MNM_WORKTREE_GUARD_WINDOW is validated as a positive integer, falling back to 90 otherwise.

Re-tested: non-worktree→pass, 1st→pass, 2nd-in-window→block, stale→pass (dir NOT deleted), invalid-window→fallback-90 block.

@dojo-code-reviewer review

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved

Approved — 0 blockers, 2 P3. Confidence: 4.60/5.00.

🔵 P3 — Minor

  • hooks/pre-task-worktree-isolation-guard.sh:77 — 🔵 P3 (minor) — Writing directly to ts via redirection truncate (>) can cause a racing process (reading ts concurrently on line 88) to read an empty file if the scheduler interleaves between the truncation and the write. Using a temp file and an atomic mv guarantees a safe, race-free, non-blocking read.

[pass 1]

  • hooks/pre-task-worktree-isolation-guard.sh:92 — 🔵 P3 (minor) — Writing directly to ts via redirection truncate (>) can cause a racing process (reading ts concurrently on line 88) to read an empty file if the scheduler interleaves between the truncation and the write. Using a temp file and an atomic mv guarantees a safe, race-free, non-blocking read.

[pass 1]


Total findings: 2 business context (2 total)

# existence, so there is no check-then-rm (TOCTOU) window in which a racing
# process's freshly-created lock could be removed.
if mkdir "$LOCK_DIR" 2>/dev/null; then
printf '%s' "$now" > "$LOCK_DIR/ts" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 P3 (minor) — Writing directly to ts via redirection truncate (>) can cause a racing process (reading ts concurrently on line 88) to read an empty file if the scheduler interleaves between the truncation and the write. Using a temp file and an atomic mv guarantees a safe, race-free, non-blocking read.

[pass 1]

held="$(cat "$LOCK_DIR/ts" 2>/dev/null || echo "$now")"
[[ "$held" =~ ^[0-9]+$ ]] || held="$now"
age=$(( now - held ))
if (( age >= WINDOW_SECONDS )); then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 P3 (minor) — Writing directly to ts via redirection truncate (>) can cause a racing process (reading ts concurrently on line 88) to read an empty file if the scheduler interleaves between the truncation and the write. Using a temp file and an atomic mv guarantees a safe, race-free, non-blocking read.

[pass 1]

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved

Approved — 0 blockers, 1 P3. Confidence: 4.80/5.00.

🔵 P3 — Minor

  • hooks/pre-task-worktree-isolation-guard.sh:62 — 🔵 P3 (minor) — Make the lock root user-specific to prevent potential permission collisions on multi-user development machines. If User A runs the hook first, they will create the shared /tmp/mnm-worktree-spawn-guard directory. Depending on their system umask, this directory might be created with restrictive permissions (like 700). If User B then runs a task on the same machine, they will not have write access to that shared directory, causing their spawns to fall back to the fail-closed path and get blocked. Appending the username (using ${USER:-shared}) ensures isolated lock directories for different users on the same host.

[pass 1]


Total findings: 1 business context (1 total)

SESSION_ID="$(printf '%s' "$INPUT_RAW" | jq -r '.session_id // "default"' 2>/dev/null || echo default)"
SESSION_ID="${SESSION_ID//[^A-Za-z0-9_.-]/_}" # sanitize for use in a path
LABEL="$(printf '%s' "$INPUT_RAW" | jq -r '.tool_input.name // .tool_input.description // "agent"' 2>/dev/null || echo agent)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 P3 (minor) — Make the lock root user-specific to prevent potential permission collisions on multi-user development machines. If User A runs the hook first, they will create the shared /tmp/mnm-worktree-spawn-guard directory. Depending on their system umask, this directory might be created with restrictive permissions (like 700). If User B then runs a task on the same machine, they will not have write access to that shared directory, causing their spawns to fall back to the fail-closed path and get blocked. Appending the username (using ${USER:-shared}) ensures isolated lock directories for different users on the same host.

[pass 1]

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.

1 participant