Skip to content

fix(run): enforce sandbox and worktree isolation - #440

Merged
solomonneas merged 2 commits into
mainfrom
codex/fix-brigade-run-isolation
Jul 22, 2026
Merged

fix(run): enforce sandbox and worktree isolation#440
solomonneas merged 2 commits into
mainfrom
codex/fix-brigade-run-isolation

Conversation

@solomonneas

@solomonneas solomonneas commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Enforce Claude headless tool restrictions and reject sandbox modes that the installed Claude CLI cannot honor.
  • Require an explicit danger-full-access request before adding permission bypass.
  • Persist a content-sensitive pre-run Git snapshot without storing raw file contents.
  • Detect branch and HEAD drift across planning, dispatch, synthesis, and finalization.
  • Report only changes observed relative to the pre-run state, with author recorded as unknown.
  • Reject dirty --allow-dirty runs in a primary checkout and direct callers to a linked worktree.

Root cause

The Claude adapter ignored read_only and sandbox. Dirty-run ground truth compared the final worktree directly with HEAD, which mixed pre-existing changes into the receipt. Run finalization also lacked branch and HEAD invariants.

Verification

  • brigade work verify run --target . --command "./scripts/verify" --capture brigade-work
  • Receipt: 20260722-171948-work-verify-20d6b2
  • Result: 3,742 passed, 3 skipped, coverage 82.60%, exit 0

Closes #437

Summary by CodeRabbit

  • New Features

    • Added run isolation checks to detect branch/commit drift during execution and stop affected runs safely.
    • Run receipts now include a saved pre-run repository snapshot for improved change attribution.
    • Ground-truth computation can subtract pre-existing modifications to isolate worker-caused changes.
  • Bug Fixes

    • brigade run --allow-dirty is now rejected for dirty primary checkouts, while remaining supported for linked worktrees.
    • Claude sandbox enforcement now fails cleanly during dispatch for unsupported or implicit write sandboxes.
    • Claude read-only mode is now treated as hard-enforced with stricter tool restrictions.

Closes #437

Co-Authored-By: Cursor <cursoragent@cursor.com>

Co-Authored-By: Codex <codex@openai.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: escoffier-labs/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b1905293-dca6-4c5f-ade2-73e8a57f2e00

📥 Commits

Reviewing files that changed from the base of the PR and between c0e69da and 8b7f933.

📒 Files selected for processing (10)
  • src/brigade/aboyeur.py
  • src/brigade/agents.py
  • src/brigade/cli/run.py
  • src/brigade/runguard.py
  • tests/test_aboyeur.py
  • tests/test_agents.py
  • tests/test_agents_model_pin.py
  • tests/test_read_only_enforcement.py
  • tests/test_run_cli.py
  • tests/test_runguard.py

📝 Walkthrough

Walkthrough

The change adds content-sensitive pre-run snapshots for ground-truth attribution, branch/HEAD drift checks throughout execution, explicit Claude sandbox enforcement, and dirty-worktree restrictions for primary checkouts.

Changes

Run isolation and sandbox enforcement

Layer / File(s) Summary
Pre-run snapshot and change attribution
src/brigade/runguard.py, tests/test_runguard.py
Captures fingerprinted Git state, detects drift, serializes snapshot metadata, and attributes tracked or untracked changes relative to the baseline.
Ground truth and drift-isolated lifecycle
src/brigade/aboyeur.py, tests/test_aboyeur.py
Uses snapshots for ground truth, persists snapshot data, records unavailable dirty-baseline diffstats, and exits on drift at lifecycle checkpoints.
Claude sandbox-aware dispatch
src/brigade/agents.py, tests/test_agents.py, tests/test_agents_model_pin.py, tests/test_read_only_enforcement.py
Requires explicit Claude sandbox settings, hardens read-only tool restrictions, and returns typed dispatch failures for unsupported modes.
Primary-checkout dirty-worktree validation
src/brigade/cli/run.py, tests/test_run_cli.py
Rejects dirty primary-checkout write runs using --allow-dirty without a linked worktree while preserving clean and linked-worktree paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested labels: coderabbit-review

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.15% 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 Title concisely matches the main change: sandbox enforcement and worktree isolation for brigade run.
Linked Issues check ✅ Passed The changes address the linked issue requirements for Claude sandbox enforcement, snapshot attribution, drift rejection, and dirty primary-checkout handling.
Out of Scope Changes check ✅ Passed The modified files and tests stay focused on run isolation and sandbox enforcement with no clear unrelated additions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-brigade-run-isolation

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

@solomonneas

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@solomonneas solomonneas self-assigned this Jul 22, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/brigade/agents.py (1)

102-122: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Unrecognized sandbox values are mislabeled as "no sandbox".

The final raise ValueError(_CLAUDE_NO_SANDBOX_ERROR) branch fires both when sandbox is None and when sandbox is any unrecognized string (e.g. a typo like "Read-Only" or "full-access"). _CLAUDE_NO_SANDBOX_ERROR says the run was "requested without an explicit sandbox," which is misleading when a sandbox value was actually supplied but not recognized.

🛠️ Proposed fix: distinguish "no sandbox" from "unrecognized sandbox"
     if sandbox == "danger-full-access":
         return [
             "claude",
             "-p",
             "--dangerously-skip-permissions",
             "--disallowedTools",
             _CLAUDE_DISALLOWED_ALWAYS,
             prompt,
         ]
-    # sandbox is None: refuse to guess between stalling and granting full access.
-    raise ValueError(_CLAUDE_NO_SANDBOX_ERROR)
+    if sandbox is not None:
+        raise ValueError(f"claude does not support sandbox {sandbox!r}. " + _CLAUDE_NO_SANDBOX_ERROR)
+    # sandbox is None: refuse to guess between stalling and granting full access.
+    raise ValueError(_CLAUDE_NO_SANDBOX_ERROR)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/brigade/agents.py` around lines 102 - 122, Update _claude_argv so sandbox
is validated separately: retain _CLAUDE_NO_SANDBOX_ERROR only when sandbox is
None, and raise a distinct error for any unrecognized non-null sandbox value.
Ensure recognized read-only, workspace-write, and danger-full-access behavior
remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/brigade/agents.py`:
- Around line 909-932: Distinguish sandbox rejection errors from other
build_argv failures in the agent dispatch handling. Update build_argv and the
exception handling around it to use or detect a dedicated
UnsupportedSandboxError, assigning unsupported-sandbox only for that case;
preserve clean failure handling for other ValueErrors with an accurate
failure_kind rather than mislabeling reasoning, model, or session validation
errors.

---

Nitpick comments:
In `@src/brigade/agents.py`:
- Around line 102-122: Update _claude_argv so sandbox is validated separately:
retain _CLAUDE_NO_SANDBOX_ERROR only when sandbox is None, and raise a distinct
error for any unrecognized non-null sandbox value. Ensure recognized read-only,
workspace-write, and danger-full-access behavior remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: escoffier-labs/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f25d304-1ce4-4aff-ad3c-7ca3f783b61c

📥 Commits

Reviewing files that changed from the base of the PR and between c0e69da and b781b28.

📒 Files selected for processing (10)
  • src/brigade/aboyeur.py
  • src/brigade/agents.py
  • src/brigade/cli/run.py
  • src/brigade/runguard.py
  • tests/test_aboyeur.py
  • tests/test_agents.py
  • tests/test_agents_model_pin.py
  • tests/test_read_only_enforcement.py
  • tests/test_run_cli.py
  • tests/test_runguard.py

Comment thread src/brigade/agents.py
Comment on lines +909 to +932
try:
argv = build_argv(
cli_ref,
prompt,
read_only=read_only,
sandbox=sandbox,
model=model,
reasoning=reasoning,
cwd=cwd,
resume_session_id=resume_session_id,
)
except ValueError as exc:
# A builder rejected the launch before spawning (e.g. claude
# workspace-write, which this CLI version cannot enforce). Fail the
# seat cleanly instead of crashing the run or stalling on a prompt.
return AgentResult(
text="",
ok=False,
detail=str(exc)[:200],
failure_phase="dispatch",
failure_kind="unsupported-sandbox",
requested_model=model,
reasoning=reasoning,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Broad except ValueError mislabels unrelated build_argv failures as unsupported-sandbox.

build_argv raises ValueError for several reasons that have nothing to do with sandbox rejection: unsupported reasoning pins (e.g. claude+reasoning="high", proven reachable by test_build_argv_rejects_reasoning_for_unsupported_adapter), ollama model-name conflicts, and grok exact-session validation (e.g. an empty resume_session_id reaches build_argv's own check since the earlier guard at Line 848 doesn't verify non-empty). All of these now get failure_kind="unsupported-sandbox" even though the real cause is unrelated, which is misleading for any caller/telemetry that branches on failure_kind.

🛠️ Proposed fix: use a dedicated exception type for sandbox rejections
+class UnsupportedSandboxError(ValueError):
+    """Raised when a builder cannot safely honor the requested sandbox/read-only mode."""
+
+
 def _claude_argv(prompt: str, read_only: bool, sandbox: str | None, cwd: Path | None) -> List[str]:
     if read_only or sandbox == "read-only":
         return ["claude", "-p", "--disallowedTools", _CLAUDE_DISALLOWED_READ_ONLY, prompt]
     if sandbox == "workspace-write":
-        raise ValueError(_CLAUDE_WORKSPACE_WRITE_ERROR)
+        raise UnsupportedSandboxError(_CLAUDE_WORKSPACE_WRITE_ERROR)
     ...
-    raise ValueError(_CLAUDE_NO_SANDBOX_ERROR)
+    raise UnsupportedSandboxError(_CLAUDE_NO_SANDBOX_ERROR)
     try:
         argv = build_argv(...)
-    except ValueError as exc:
+    except UnsupportedSandboxError as exc:
         return AgentResult(
             text="",
             ok=False,
             detail=str(exc)[:200],
             failure_phase="dispatch",
             failure_kind="unsupported-sandbox",
             requested_model=model,
             reasoning=reasoning,
         )

Note: narrowing the except to UnsupportedSandboxError alone would let other ValueErrors (reasoning/model/ollama misconfiguration) propagate uncaught again, as they apparently did before this diff. If that regression is unacceptable, keep the broad except ValueError but branch the failure_kind on isinstance(exc, UnsupportedSandboxError) instead of hardcoding it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/brigade/agents.py` around lines 909 - 932, Distinguish sandbox rejection
errors from other build_argv failures in the agent dispatch handling. Update
build_argv and the exception handling around it to use or detect a dedicated
UnsupportedSandboxError, assigning unsupported-sandbox only for that case;
preserve clean failure handling for other ValueErrors with an accurate
failure_kind rather than mislabeling reasoning, model, or session validation
errors.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 8b7f933. Sandbox rejections now use UnsupportedSandboxError and retain failure_kind=unsupported-sandbox; other build_argv ValueErrors return invalid-dispatch-args. Regression tests cover unsupported reasoning and unknown CLI cases.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR enforces sandbox and worktree isolation for runs. The main changes are:

  • Restrict Claude tools in read-only mode and reject unsupported sandbox requests.
  • Capture and persist content-sensitive pre-run Git snapshots.
  • Report file changes relative to the pre-run state.
  • Detect branch and HEAD drift throughout a run.
  • Reject dirty write runs in primary checkouts.

Confidence Score: 5/5

This looks safe to merge.

  • The snapshot comparison now covers restored tracked files and deleted untracked files.
  • Final Git query failures leave ground truth unavailable instead of reporting a clean result.
  • Persisted snapshots include fingerprints without storing raw file contents.
  • Claude read-only runs deny built-in mutating tools and configured extension tools.

Important Files Changed

Filename Overview
src/brigade/runguard.py Adds content-sensitive snapshots, baseline-relative change detection, and branch and HEAD drift checks.
src/brigade/aboyeur.py Persists pre-run state, fails ground-truth collection closed, and checks repository drift across run phases.
src/brigade/agents.py Enforces Claude read-only restrictions and rejects unsupported sandbox configurations before dispatch.
src/brigade/cli/run.py Prevents dirty write runs in primary checkouts while allowing linked-worktree isolation.

Reviews (3): Last reviewed commit: "fix(run): close isolation review gaps" | Re-trigger Greptile

Comment thread src/brigade/runguard.py
Comment thread src/brigade/runguard.py
Comment thread src/brigade/runguard.py
Comment thread src/brigade/agents.py Outdated
Co-Authored-By: Cursor <cursoragent@cursor.com>
@solomonneas

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@solomonneas

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@solomonneas
solomonneas merged commit e41c059 into main Jul 22, 2026
29 checks passed
@solomonneas
solomonneas deleted the codex/fix-brigade-run-isolation branch July 26, 2026 20:37
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.

brigade run must enforce sandbox and dirty-worktree isolation

1 participant