Skip to content

Umbrella: merge guard end-to-end + hook contract documentation + governance meta-pattern #677

@michael-wojcik

Description

@michael-wojcik

Status (2026-05-11): Phase 1 + 2 + 5 ✅ DONE. Phase 4 tracked as #704. Phase 3 still future work. See status comment for full follow-up linkage.

Why this exists

The merge-guard mechanism has two distinct bugs that prevent it from working end-to-end as designed:

Either bug alone breaks merges. Together they make the merge guard a no-op security theater: when it does block, the failure is misleading; when it appears to work, it's because operators learned workarounds.

This umbrella issue bundles Option B — fix both #665 and #676 in a single PR — plus the surrounding gaps that fixing the immediate bugs does NOT address.

Scope

Phase 1 — Pair the two bug fixes (the merge guard works end-to-end) ✅ DONE (PR #697)

After Phase 1: the merge guard works as designed. AskUserQuestion + "Yes, merge" + gh pr merge flow succeeds without manual intervention. ✅ verified in v4.1.8 ship.

Phase 2 — Behavioral-change communication ✅ DONE (PR #697 + v4.1.8 release)

Phase 3 — Captured-fixture hardening for ALL hooks ⏳ NOT STARTED

The root cause of #676 is that test_merge_guard.py's 13+ fixtures use tool_output — a fictional shape — and the code matches the fixtures. Synthetic fixtures don't catch platform-shape drift. Only fixtures captured from real Claude Code stdin during actual hook execution will detect when the platform schema changes underneath us.

  • Apply the #612 logging-shim pattern (already proven in bootstrap_marker_writer's captured-fixture follow-up Capture real UserPromptSubmit stdin fixture per #664 §8.7 #672) to every PostToolUse hook in pact-plugin/hooks/:
    • merge_guard_post.py
    • wake_lifecycle_emitter.py
    • task_lifecycle_gate.py
    • auditor_reminder.py
    • file_size_check.py
    • track_files.py
    • teachback_check.py
    • file_tracker.py
  • Each hook gets a captured-from-production fixture in pact-plugin/tests/fixtures/{hook_name}_stdin.json representing the actual stdin shape Claude Code sends. The #612 shim runs on a separate scratch branch, captures during a fresh-startup PACT session, then is removed.
  • Each hook's test suite gets at least one parametrized "fixture-load + dispatch" test that exercises the hook against the captured fixture.
  • CI guards: a parametrized integration test over every PostToolUse hook that fails if a hook reads a stdin field that's not in the captured fixture's shape.

This is the only durable defense against the next silent platform-schema drift. Needs separate plan-mode + scratch branches per hook; cannot run inside the same session as the fix.

Phase 4 — Hook contract documentation ⏳ NOT STARTED — tracked as #704

The convention "PostToolUse hooks read tool_response" lives implicitly in wake_lifecycle_emitter.py's docstring. The 4 docstring-drift cases (auditor_reminder.py etc.) all carried the same incorrect template line — proving the convention propagates by template-copying without verification. PR #697 swept the immediate drift but the convention itself still needs canonical documentation.

See #704 for full Phase 4 scope (includes additions surfaced in PR #697: shared extract_tool_response helper pattern, SECURITY/PLANNING_SCAN_PATH_EXCLUDES bootstrap-self-block pattern, strict cross-op match enforcement, deny-compound-destructive pattern, eval+heredoc pre-strip detection).

Phase 5 — Governance / meta-pattern ✅ DONE (audit comment 2026-05-07)

The merge_guard_post bug and the lead-owns-commits violation pattern (#675) and the persona §2 /clear factual error (closed in PR #671) are all instances of the same meta-pattern: rules documented in prose with no mechanical enforcement, where the runtime cost of the violation is invisible to operators until something else surfaces it.

  • Inventory current PACT rules that are prose-only (delivered as audit comment 2026-05-07 — categorized 30+ rules into A/B/C/D enforcement-state buckets)
  • For each, propose either a test, a hook, or an explicit acceptance test (delivered in audit comment)
  • File any net-new mechanical-enforcement issues that fall out of the inventory (C1 + C2 follow-ups recommended; C3 folded into C2)

Cross-references

Closed by PR #697 (Phase 1):

Closed by PR #697 (cycle-2 + cycle-3 security remediation):

Open follow-ups from PR #697 review:

Original cross-references (pre-PR #697):

Suggested PR sequencing

  1. PR Claude Code: not strictly follow framework #1: Phase 1 + Phase 2. DONE as PR fix(merge-guard): pair #665 + #676 fixes; bump to 4.1.8 (#677 PR #1) #697 → v4.1.8. Original scope expanded mid-flight via 4 cycles of peer-review remediation to also close 6 of 8 security findings (F-1 through F-7 except F-6) — became 14 commits.
  2. PR feat: Add PACT slash commands and update PACT_Prompt with explicit agent and PR workflows #2: Phase 4 (SHARED_CONVENTIONS.md). NEXT — tracked as Phase 4: SHARED_CONVENTIONS.md hook contract documentation (deferred from #677 umbrella) #704. Zero code change; pure doc.
  3. PR Validate skill auto-activation with real user tasks #3: Phase 3 (captured fixtures + integration tests). Larger; needs separate scratch branches per hook for capture, then a consolidation PR with all fixtures. This is the biggest scope and deserves its own architecture-phase planning.
  4. Phase 5 delivered as audit comment 2026-05-07. C1 + C2 follow-ups recommended in that comment.

Acceptance for this umbrella

Out of scope

Background

This umbrella was filed during the post-merge investigation following PR #671 (#664 hook-driven bootstrap marker write). The merge-guard bug was discovered when the merge for #671 was blocked even after a textually-perfect AskUserQuestion + affirmative answer. Manual token write salvaged the merge; investigation traced the post-hook field-name mismatch and surfaced the broader docstring drift + the meta-pattern of prose-rules-without-mechanical-enforcement that has been visible in the merge guard, in lead-owns-commits (#675), in the SendMessage-then-TaskUpdate ordering invariant (#674), and in the persona §2 /clear factual error (closed in PR #671).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions