Skip to content

cmux guards: run merge guard hooks from the feat-cmux-next tip - #23316

Merged
teamleaderleo merged 4 commits into
feat-cmux-nextfrom
claude/guard-hooks-from-base
Oct 21, 2026
Merged

teamleaderleo merged 4 commits into
feat-cmux-nextfrom
claude/guard-hooks-from-base

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

The merge guards now run from the feat-cmux-next tip, never from the session's checkout. palette-audit found that .claude/settings.json ran "$CLAUDE_PROJECT_DIR"/scripts/ci/merge-compile-guard-hook.sh and base-merge-warning-hook.sh from the session's checkout. A session in a pull request's worktree therefore ran that branch's copy of the guard: a branch made before a guard fix kept the old guard, and a pull request could edit the guard that judges its own merge.

Fix

  • The one PreToolUse(Bash) hook reads scripts/ci/guards-from-base.sh from origin/feat-cmux-next into a temporary file and runs it. For a merge, it first fetches feat-cmux-next, bounded to 10 s. Commands that mention neither a merge nor update-branch exit before git runs.
  • scripts/ci/guards-from-base.sh extracts both hooks and their Python guards from that same commit into a temporary directory, with git archive, and runs them. The first answer, a refusal or a note, is the answer.
  • A merge whose guards can't be read from the base is refused, as merge_compile_guard.py refuses one it can't read. The update-branch warning only warns.

Known limit: .claude/settings.json itself still loads from the session's checkout, so a branch cut before this change keeps the old hooks until it merges the base. A PR could also delete the hook.

Tests: tests/test_guard_hooks_from_base.py is new and wired into cmux-next.yml. It builds a local remote and a PR worktree whose branch rewrites both guards and their hook to let everything through. The test checks:

  • the base copy still judges that PR's merge;
  • a guard fix pushed to the base later judges a branch made before it;
  • the update-branch warning also comes from the base;
  • unrelated commands pass;
  • a stale origin/feat-cmux-next is fetched before a merge;
  • a merge whose guards can't be read is denied, while update-branch is not blocked;
  • no PreToolUse hook names a script under $CLAUDE_PROJECT_DIR.

Red: python3 tests/test_guard_hooks_from_base.py on 284ed32 (base code) failed with "the pull request's own copy of the guard judged its merge", and on 648e6f7 two cases failed (the stale origin and unreadable guards both failed open). Green on the head: 8 OK.

Observed / Not run

  • Observed:
    • The red and green runs above.
    • tests/test_merge_compile_guard.py, tests/test_base_merge_warning.py and tests/test_format_post_edit.py all pass (1 skip: webviews packages are not installed).
    • An independent review subagent passed it (SHIP at b73989b, after a BLOCK at 648e6f7 for the fail-open fixed above).
  • Not run: a live Claude Code session loading the new settings. The test runs the exact hook command from settings.json with bash.

Changelog

none

Agent session: huge-blue

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Merge guards now run from the feat-cmux-next tip instead of the session's checkout, so a pull request's branch can no longer run its own edited copy of the guard that judges its merge.

  • The PreToolUse hook fetches origin/feat-cmux-next (bounded to 10 s) and runs scripts/ci/guards-from-base.sh from that commit, which executes both merge guards via git archive into a temporary directory.
  • Commands that mention neither a merge nor update-branch exit before git runs, so unrelated commands are unaffected.
  • A merge whose guards can't be read from the base is denied; the update-branch warning only warns.
  • Known limit: .claude/settings.json still loads from the session's checkout, so a branch cut before this change keeps the old hooks until it merges the base.

Tests

  • New tests/test_guard_hooks_from_base.py (wired into cmux-next.yml) covers base-copy judging of merges, guard fixes on the base applying to older branches, stale origin fetching, fail-closed merges, and no hook running a script from the checkout.

Written for commit b73989b. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 4 commits October 21, 2026 10:24
…request's checkout

Red on fc8a36d: .claude/settings.json runs scripts/ci/*-hook.sh from $CLAUDE_PROJECT_DIR, so a
pull request's own edit of the guard judges its merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c6d-a1d2-7fc2-9458-92c6bdcb7b4d&host=huge-blue
…ession's checkout

.claude/settings.json ran scripts/ci/merge-compile-guard-hook.sh and base-merge-warning-hook.sh
from $CLAUDE_PROJECT_DIR, so a session in a pull request's worktree ran that branch's guard: a
branch made before a guard fix kept the old one, and a pull request could edit the guard that
judges its own merge (palette-audit). The hook now reads scripts/ci/guards-from-base.sh from
origin/feat-cmux-next (fetched first for a merge) and that runs both guards and their hooks
from the same commit, out of a temporary directory. Commands that mention neither a merge nor
update-branch exit before git runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c6d-a1d2-7fc2-9458-92c6bdcb7b4d&host=huge-blue
… origin is fetched first

Red on 648e6f7 (review): both failed open with only a stderr line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15c6d-a1d2-7fc2-9458-92c6bdcb7b4d&host=huge-blue
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by land-and-ci on huge-blue · session

@teamleaderleo

teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Review (subagent, correctness first): SHIP at b73989b

I re-reviewed the whole diff at b73989b and the merge fail-open from my earlier BLOCK is fixed.

The four tests all pass: test_guard_hooks_from_base, test_merge_compile_guard and test_base_merge_warning OK, and test_format_post_edit OK with 1 skip ("webviews packages are not installed").

  • Merges now fetch first. For *"pr merge"*|*"/merge"*, the hook in .claude/settings.json runs timeout 10 git fetch origin feat-cmux-next before git show. A clone whose origin/feat-cmux-next predates this change now picks up the script itself.
  • A merge the guards can't judge is refused. I ran the inline hook taken from settings.json against an empty repo with no origin/feat-cmux-next:
    • A gh pr merge got deny JSON that parses cleanly, with exit 0.
    • An update-branch got only a stderr warning and exit 1, which doesn't block.
  • scripts/ci/guards-from-base.sh handles its own failures the same way. Its cannot() helper refuses a merge with deny JSON and exit 0 when there is no base ref, no temp dir, or the archive or extract fails. pipefail makes a failed git archive | tar count.
  • The trap now quotes $f, and the JSON escaping in settings.json is correct.

Nothing else changed from my earlier pass: no checkout code runs, and the filters, timeouts and output order are unchanged.

Two limits remain, and neither blocks:

  • A crashing guard still lets the merge through. If a guard hook extracted from base crashes with a nonzero exit, Claude Code treats that as a non-blocking error. The old hooks behaved the same way.
  • settings.json still loads from the session's checkout. A branch cut before this change keeps the old hooks, and a PR could delete the hook. This PR can't fix that.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review b73989b: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr23316 -f ref=claude/guard-hooks-from-base -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); NEW: red check: Script tests (pin-cmux-tui, embed-cef, ensure-cef store) (+1 more) is not red on the base fb47146; do not merge until green; see lane-rules

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026

Copy link
Copy Markdown

Found 3 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpdmwxetz7/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpx_esbju9/summary-fail.json (live):/summary-fail.json (live): View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-9nv6g created for this PR by the cmux coordinator.

@teamleaderleo
teamleaderleo merged commit 6f55716 into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the claude/guard-hooks-from-base branch October 21, 2026 11:12
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
#23316 added tests/test_guard_hooks_from_base.py without a
tests/test-execution.toml entry. The registry check fails on
feat-cmux-next, and every branch's pre-commit hook adds the entry on its
own, so open PRs conflict with each other on the registry. Its lane is
linux-guard, as the hook registers it and like the other guard tests.


Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d18-aabc-7850-af29-f0655b1d2a1a&host=cosmic-gray

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant