Skip to content

fix(sdlc): collapse 6-way gate fanout to a stable abs-path shim + hooks-doctor (INV-5)#3832

Merged
ryanklee merged 1 commit into
mainfrom
beta/reform-improve-gate-shim-collapse-20260601
Jun 1, 2026
Merged

fix(sdlc): collapse 6-way gate fanout to a stable abs-path shim + hooks-doctor (INV-5)#3832
ryanklee merged 1 commit into
mainfrom
beta/reform-improve-gate-shim-collapse-20260601

Conversation

@ryanklee
Copy link
Copy Markdown
Collaborator

@ryanklee ryanklee commented Jun 1, 2026

Reform FM-6 — the gate-fanout collapse

cc-task-gate.sh was physically copied into ~26 worktrees, so the fleet ran six gate versions (427/651/779/786/832/905 lines) and the AGENT-RUNNING lanes carried the oldest 427-line gate, which lacks is_cognition_pathINV-5 (cognition-always-writable) was silently violated on 9 lanes. The master design promised "hooks → stable abs-path shims" + a hooks-doctor drift detector; neither existed. This builds them.

What changed

  • cc-task-gate.sh → thin stable-abs-path SHIM. The gate logic moves to cc-task-gate.impl.sh. The shim resolves ONE canonical impl at $HAPAX_CANONICAL_HOOKS (default ~/.local/lib/hapax/hooks), canonical-first (so the LIVE hook escapes rebuild-worktree drift), with a co-located-impl fallback (hermetic for fresh checkouts/CI) and a fail-open-with-ledger floor — never fail-stuck, honoring INV-5. An -ef self-guard prevents a misconfigured-canonical exec loop. "Update the gate" is now a one-file change, not 26.
  • hooks-doctor.sh — the drift detector: --classify / --check (CI refusal) / --deploy-canonical (refuses any impl missing the INV-5 carve-out) / --fanout (re-shims stale lanes). Wired as SessionStart + CI (pytest) + a 6h timer sweep.
  • escape-grant.sh — repo-root resolution gains an explicit override + known-checkout fallback (gated on shared/ presence) so the canonical-deployed gate keeps honoring escape grants (INV-4) from ~/.local/lib/hapax/hooks. In-repo behavior unchanged.
  • hapax-post-merge-deploy — redeploys the canonical gate closure (materialized from the merge SHA) whenever the impl or its sourced siblings change.
  • hapax-hooks-doctor.{service,timer} — 6h advisory fleet drift sweep (catches a rebuild clobbering canonical, NEW-CATCH-3).
  • Gate-logic tests repointed at cc-task-gate.impl.sh; the peer-glob coherence lint follows the vault globs to the impl; install-claude-code.sh registers the SessionStart hook.

Acceptance criteria (delivered by this PR)

  • Every worktree's gate becomes a thin shim resolving to one canonical implementation; updating the gate is a one-file change.
  • hooks-doctor detects + refuses on gate drift (SessionStart + CI + timer).
  • INV-5 cognition carve-out present fleet-wide via the canonical resolution (verified end-to-end: shim → canonical allows /dev/shm cognition writes, blocks out-of-scope writes).
  • Ruff + tests pass.

Post-merge runtime (this lane, immediately after merge): hooks-doctor --deploy-canonical + --fanout to re-shim the live fleet (fixes INV-5 on the 9 stale lanes now), wire the SessionStart drift hook, then hooks-doctor --check to witness a clean fleet, and cc-close.

Test evidence

  • New: tests/hooks/test_cc_task_gate_shim.py (8) + tests/hooks/test_hooks_doctor.py (10) — shim resolution/precedence/stdin+exit passthrough/fail-open/self-loop; doctor classify/check-refusal/deploy/fanout. All pass.
  • Repointed gate tests + peer-glob + post-merge-deploy + agent-registry: green. ruff check / ruff format --check: clean. systemd units verify clean.

Note (pre-existing, unrelated)

tests/hooks/test_codex_hook_adapter.py::test_session_start_returns_codex_additional_context can time out on its hard 15s ceiling under load — session-context.sh (unchanged here) takes ~8s alone and the full adapter path completes in ~14s (rc=0, not a hang). Not in this changeset.

Task: reform-improve-gate-shim-collapse-20260601 · AuthorityCase: CASE-CROSS-RUNTIME-COMMS-001

🤖 Generated with Claude Code

…ks-doctor (INV-5)

Reform FM-6: cc-task-gate.sh was physically copied into ~26 worktrees, so the
fleet ran SIX gate versions (427/651/779/786/832/905 lines) and the oldest lanes
silently violated INV-5 (cognition-always-writable) — their 427-line gate
predates is_cognition_path. The promised "hooks -> stable abs-path shims" + a
hooks-doctor drift detector were never built; this builds them.

- Move the gate logic to hooks/scripts/cc-task-gate.impl.sh. cc-task-gate.sh
  becomes a thin stable-abs-path SHIM resolving ONE canonical impl at
  $HAPAX_CANONICAL_HOOKS (default ~/.local/lib/hapax/hooks), canonical-first so
  the LIVE hook escapes rebuild-worktree drift, with a co-located-impl fallback
  for fresh checkouts/CI and a fail-open-with-ledger floor (never fail-stuck →
  honors INV-5). A -ef self-guard prevents a misconfigured-canonical exec loop.
- Add hooks/scripts/hooks-doctor.sh: classify / --check (CI refusal) /
  --deploy-canonical / --fanout. Detects a worktree running a stale gate and
  refuses gate drift; deploy refuses any impl missing the INV-5 carve-out.
- escape-grant.sh: resolve the repo root (for shared.governance.coord_capabilities)
  via an explicit override + a known-checkout fallback gated on shared/ presence,
  so the canonical-deployed gate keeps honoring escape grants (INV-4) when it runs
  from ~/.local/lib/hapax/hooks where ../.. has no shared/ tree. In-repo behavior
  is unchanged.
- hapax-post-merge-deploy: redeploy the canonical gate closure when the impl or
  its sourced siblings change (materialized from the merge SHA), via
  hooks-doctor --deploy-canonical.
- hapax-hooks-doctor.{service,timer}: 6h advisory fleet drift sweep (catches a
  rebuild clobbering canonical, NEW-CATCH-3).
- install-claude-code.sh: register hooks-doctor --session as a SessionStart hook.
- Repoint gate-logic tests at cc-task-gate.impl.sh; peer-glob lint follows the
  vault globs to the impl. New tests pin shim resolution/passthrough/fail-open and
  doctor classify/check/deploy/fanout.

Acceptance: every worktree's gate is a shim resolving to one canonical impl
(one-file update); hooks-doctor detects + refuses drift (SessionStart + CI +
timer); the INV-5 cognition carve-out is present fleet-wide via that resolution.

Task: reform-improve-gate-shim-collapse-20260601
AuthorityCase: CASE-CROSS-RUNTIME-COMMS-001
Parent-Spec: coordination-reform-master-design-2026-05-30 (FM-6)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors the CC task gate from a monolithic inline script into a canonical deployable implementation (cc-task-gate.impl.sh) backed by a minimal shim (cc-task-gate.sh), adds a doctor tool for drift detection and fleet sync, integrates periodic health checks, and provides comprehensive test coverage for both direct implementation and shim resolution behavior.

Changes

CC Task Gate Canonical Deployment & Drift Management

Layer / File(s) Summary
Canonical Gate Implementation
hooks/scripts/cc-task-gate.impl.sh
Complete 916-line fail-closed gating logic: mutating-tool classification, cognition/diagnostic allowlist, emergency bypasses, governance-intake bootstrap, shadow decision logging, session identity resolution, task note parsing, status/authority/stage/mutation authorization enforcement, direct file scope verification, and state transitions.
Shim Redirection & Canonical Resolution
hooks/scripts/cc-task-gate.sh
Rewritten to 45-line stateless shim that resolves canonical gate from $HAPAX_CANONICAL_HOOKS, execs it, and falls back to co-located cc-task-gate.impl.sh; includes self-exec guard and fail-open-with-ledger behavior.
Helper Function Enhancements
hooks/scripts/escape-grant.sh
_escape_grant_repo_root() now uses multi-stage resolution: prefers HAPAX_COORD_REPO_ROOT override, attempts in-repo relative resolution, falls back to known worktrees, validates shared/ presence.
Gate Health & Deployment Doctor Tool
hooks/scripts/hooks-doctor.sh
New 274-line tool for drift detection and canonical deployment: classifies gate health (shim/WARN/CRITICAL), validates repository state, checks canonical deployment, syncs lane worktrees, generates manifests, and dispatches on --session, --check, --classify, --deploy-canonical, --fanout modes.
Systemd Periodic Gate Health Service
systemd/units/hapax-hooks-doctor.{service,timer}
Oneshot service + 6-hour timer runs hooks-doctor.sh --session --notify to detect and report fleet drift with configurable jitter and persistence.
Post-Merge & Installation Deployment
scripts/hapax-post-merge-deploy, scripts/install-claude-code.sh, scripts/check-peer-glob-coherence.py
Detects gate closure changes on merge, stages commit-time gate files, invokes hooks-doctor.sh --deploy-canonical; adds hooks-doctor.sh --session to Claude Code SessionStart hooks; updates peer-glob linting to reference implementation.
Test Suite Migration to Implementation Path
tests/hooks/test_*.py, tests/scripts/test_*.py, tests/test_peer_glob_coherence.py
Updates all existing gate tests to invoke cc-task-gate.impl.sh directly (bypassing shim) for hermetic decision matrix validation; added comments clarifying shim resolution is tested separately.
Shim Resolution & Fallback Integration Tests
tests/hooks/test_cc_task_gate_shim.py
New 159-line test suite validating shim contract: marker presence, canonical/fallback resolution, cognition-surface write allowance, stdin/exit-code passthrough, fail-open behavior, and self-exec loop prevention.
Doctor Tool Integration Tests
tests/hooks/test_hooks_doctor.py
New 201-line test suite exercising doctor modes: --classify (health status), --check (repository validation), --deploy-canonical (canonical landing with manifest), --fanout (lane-worktree sync with idempotency).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • hapax-systems/hapax-council#3805: Introduced the EscapeGrant-aware _emit_block and retro-grant obligation logic for emergency bypasses that the main PR's implementation now uses.
  • hapax-systems/hapax-council#3830: Changed agent-role.sh role-inference behavior directly affecting session-identity resolution in the main PR's new cc-task-gate.impl.sh.
  • hapax-systems/hapax-council#3741: Previous gate enhancements (cognition fail-open, mutation-scope tweaks, route_metadata_schema defaulting, blank-stage derivation) are now embedded in the canonical implementation, moving code from shim to standalone form.

Poem

🐰 A gate once tangled, now split apart,
Shim whispers forward, to impl's heart.
Doctor tends the fleet with timely grace,
Drift detected swift in every place.
Canonical deploys where tasks align—
One file, one truth, by design. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% 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
Title check ✅ Passed The title clearly summarizes the main change: replacing duplicated gate fanout across worktrees with a stable shim and hooks-doctor drift detector, addressing INV-5 invariant.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description comprehensively covers all required sections: summary, acceptance criteria with checkboxes, test evidence, authority case details, and implementation changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 beta/reform-improve-gate-shim-collapse-20260601

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3875b00499

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +181 to +183
for s in agent-role.sh escape-grant.sh hooks-doctor.sh; do
[[ -r "$src/$s" ]] && install -m 0755 "$src/$s" "$CANONICAL_DIR/$s"
done
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 Badge Include the bootstrap helper in the canonical gate closure

When the canonical gate is deployed, this closure only installs the impl and three shell siblings, but cc-task-gate.impl.sh now executes $SCRIPT_DIR/cc-task-gate-bootstrap.py on every protected mutation before claim validation. In the canonical layout (~/.local/lib/hapax/hooks) that Python helper is absent, so python3 returns an error, the *) exit 2 branch is taken, and all non-cognition mutating tool calls are blocked even for valid claimed sessions. The post-merge staging list also omits this file, so the helper needs to be staged and installed with the rest of the closure.

Useful? React with 👍 / 👎.

fi
done
if [ -x "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" ]; then
bash "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" --deploy-canonical --from "$GATE_STAGE" \
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 Badge Fan out the shim when deploying the gate collapse

For the merge that converts cc-task-gate.sh into a shim, this deploy path only refreshes the canonical implementation; it never runs the new hooks-doctor --fanout path. Existing lane worktrees whose hooks/scripts/cc-task-gate.sh is still a full stale gate do not resolve the canonical copy at all, and the added SessionStart/timer doctor paths are advisory-only, so those lanes continue running the old gate until someone manually fanouts/rebases them. The post-merge deploy should rewrite lane gates to the shim after a successful canonical deploy so the stable-abs-path collapse actually reaches active worktrees.

Useful? React with 👍 / 👎.

if [[ -n "$ROOT_OVERRIDE" ]]; then
REPO_ROOT="$ROOT_OVERRIDE"
else
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." 2>/dev/null && pwd || echo "$SCRIPT_DIR")"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve the doctor symlink before deriving repo root

After --deploy-canonical creates ~/.local/bin/hapax-hooks-doctor as a symlink to this script, invoking that documented command without --root makes SCRIPT_DIR resolve to the symlink directory (~/.local/bin), so this fallback root becomes ~/.local instead of the council checkout. In that installed path, hapax-hooks-doctor --check, --fanout, or the warning-suggested --deploy-canonical inspect ~/.local/hooks/scripts and fail/miss the worktree fleet unless the caller knows to pass overrides; resolve the script path or use a stable checkout default before computing ../...

Useful? React with 👍 / 👎.

Comment on lines +780 to +781
bash "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" --deploy-canonical --from "$GATE_STAGE" \
|| echo "warning: canonical gate deploy failed" >&2
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate canonical deploy failures

If hooks-doctor --deploy-canonical refuses the staged gate or cannot install into the canonical directory, this || echo converts the failure into a successful post-merge deploy and the script continues to mark work as done. For commits that changed the gate closure, that leaves the live canonical gate stale while the deploy trace reports completion, contrary to this script's contract that deploy-step failures exit nonzero; let the failure propagate after printing context.

Useful? React with 👍 / 👎.

@ryanklee ryanklee added this pull request to the merge queue Jun 1, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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 `@hooks/scripts/cc-task-gate.impl.sh`:
- Around line 371-376: The lease heartbeat currently touches "$claim_file"
before the task note is located and before validating assigned_to and status,
allowing stale or reassigned claims to be kept alive; move the touch call so it
only runs after the code has found the task note and confirmed the claim's
assigned_to matches and status is non-terminal, and keep it best-effort
(2>/dev/null || true) so it doesn't block decision logic—i.e., perform the
validations (assigned_to/status) first, then call touch "$claim_file" to refresh
the lease only for a proven-valid claim.
- Around line 248-250: The canonical deploy currently omits the helper script
used by cc-task-gate.impl.sh, causing python3
"$SCRIPT_DIR/cc-task-gate-bootstrap.py" to fail; update the canonical deployment
closure used by hapax-post-merge-deploy / hooks-doctor.sh --deploy-canonical so
that cc-task-gate-bootstrap.py is installed alongside cc-task-gate.sh (and the
other gate files), or alter the deploy manifest that builds the closure to
explicitly include cc-task-gate-bootstrap.py; ensure the installed SCRIPT_DIR
contains cc-task-gate-bootstrap.py so the cc-task-gate.impl.sh bootstrap
invocation succeeds.

In `@hooks/scripts/hooks-doctor.sh`:
- Around line 177-179: The script currently performs mutating operations (e.g.,
mkdir -p "$CANONICAL_DIR" and install -m 0755 "$src/cc-task-gate.impl.sh"
"$CANONICAL_DIR/cc-task-gate.sh") and other state-changing writes at the other
noted locations (lines around 188-189 and 210-212) without checking their exit
status; update each mutating command so failures are detected and cause the
script to fail the deployment/fanout instead of printing success—for each
command (mkdir, install and the other write/replace operations referenced) check
the command's exit code and on non-zero log an error and exit non-zero (or use a
strict error handler like set -e preceded by explicit error messages) so writes
cannot be silently ignored.
- Around line 181-183: The loop that installs closure siblings (for s in
agent-role.sh escape-grant.sh hooks-doctor.sh) currently treats missing source
files as optional; change it to require all siblings exist and are readable in
$src before proceeding: iterate the same list, test [[ -r "$src/$s" ]] and if
any check fails, print a clear error mentioning the missing $s and exit 1
(refuse deploy); only when all checks pass run the install -m 0755 "$src/$s"
"$CANONICAL_DIR/$s" for each file.

In `@scripts/hapax-post-merge-deploy`:
- Around line 779-783: The post-merge deploy currently swallows failures by
appending "|| echo ..." when invoking hooks-doctor.sh; change that so any
non-zero exit from the canonical gate deploy propagates to the caller instead of
being downgraded to a warning. Specifically, in the block that checks executable
"$GATE_STAGE/hooks/scripts/hooks-doctor.sh" and in the other similar block near
the later occurrence, replace the "bash ... || echo 'warning: canonical gate
deploy failed' >&2" pattern with logic that runs bash, captures its exit status,
prints a meaningful error to stderr, and then exits with that same non-zero
status (or simply let the script exit non-zero), ensuring failures bubble up to
the caller.

In `@systemd/units/hapax-hooks-doctor.service`:
- Line 18: The ExecStart shell currently ends with `exit 0` when no
hooks-doctor.sh is found, which treats missing doctor script as success; update
the hapax-hooks-doctor.service ExecStart command so the final fallback exits
non‑zero (e.g., `exit 1`) to make the unit fail when hooks-doctor.sh cannot be
resolved, keeping the rest of the loop and search order for
"$HOME/.local/lib/hapax/hooks",
"$HOME/.cache/hapax/rebuild/worktree/hooks/scripts", and
"$HOME/projects/hapax-council/hooks/scripts" intact and ensuring systemd marks
the unit failed instead of green.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: add855a9-4028-4281-a77f-1570a2bac33d

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6bcbc and 3875b00.

📒 Files selected for processing (18)
  • hooks/scripts/cc-task-gate.impl.sh
  • hooks/scripts/cc-task-gate.sh
  • hooks/scripts/escape-grant.sh
  • hooks/scripts/hooks-doctor.sh
  • scripts/check-peer-glob-coherence.py
  • scripts/hapax-post-merge-deploy
  • scripts/install-claude-code.sh
  • systemd/units/hapax-hooks-doctor.service
  • systemd/units/hapax-hooks-doctor.timer
  • tests/hooks/test_antigrav_gate_enforcement.py
  • tests/hooks/test_authority_case_gate.py
  • tests/hooks/test_cc_task_gate.py
  • tests/hooks/test_cc_task_gate_bootstrap.py
  • tests/hooks/test_cc_task_gate_shim.py
  • tests/hooks/test_hooks_doctor.py
  • tests/hooks/test_sdlc_frictionless_loosening.py
  • tests/scripts/test_codex_crashout_prevention.py
  • tests/test_peer_glob_coherence.py

Comment on lines +248 to +250
set +e
_bootstrap_output="$(
printf '%s' "$input" | python3 "$SCRIPT_DIR/cc-task-gate-bootstrap.py" 2>&1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -a '^(hapax-post-merge-deploy|hooks-doctor\.sh)$' scripts hooks 2>/dev/null || true
rg -n -C2 'cc-task-gate-bootstrap\.py|cc-task-gate\.impl\.sh|agent-role\.sh|escape-grant\.sh|deploy-canonical|canonical gate closure' scripts hooks

Repository: hapax-systems/hapax-council

Length of output: 23252


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact bootstrap call + handling logic in cc-task-gate.impl.sh
sed -n '230,310p' hooks/scripts/cc-task-gate.impl.sh

# Show how the shim resolves/executed canonical gate
sed -n '1,120p' hooks/scripts/cc-task-gate.sh

# Show what hooks-doctor.sh deploys into the canonical dir
sed -n '140,220p' hooks/scripts/hooks-doctor.sh

# Confirm whether cc-task-gate-bootstrap.py is present in canonical-closure lists elsewhere
rg -n 'cc-task-gate-bootstrap\.py' hooks/scripts scripts | head -n 50

# Confirm whether hapax-post-merge-deploy’s canonical closure list includes the bootstrap helper
sed -n '730,810p' scripts/hapax-post-merge-deploy

Repository: hapax-systems/hapax-council

Length of output: 13661


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate the bootstrap helper in the repo
fd -a 'cc-task-gate-bootstrap\.py$' . || true

# 2) Check canonical deploy closure sources for bootstrap inclusion
rg -n --no-heading 'cc-task-gate-bootstrap\.py' scripts hooks || true

# 3) Inspect deploy_canonical copy list in hooks-doctor.sh
rg -n --no-heading 'deploy_canonical|for s in agent-role\.sh escape-grant\.sh' hooks/scripts/hooks-doctor.sh
sed -n '150,210p' hooks/scripts/hooks-doctor.sh

# 4) Inspect hapax-post-merge-deploy canonical closure copy list
rg -n --no-heading 'canonical gate closure|GATE_STAGE|cc-task-gate\.impl\.sh|agent-role\.sh|escape-grant\.sh|hooks-doctor\.sh' scripts/hapax-post-merge-deploy
sed -n '740,810p' scripts/hapax-post-merge-deploy

# 5) Confirm cc-task-gate.impl.sh resolves SCRIPT_DIR and runs the helper from there
sed -n '35,80p' hooks/scripts/cc-task-gate.impl.sh
sed -n '235,270p' hooks/scripts/cc-task-gate.impl.sh

# 6) Confirm python3 exit code when the target file is missing
python3 /path/does/not/exist/cc-task-gate-bootstrap.py >/dev/null 2>&1
echo "python_missing_exit_code=$?"

Repository: hapax-systems/hapax-council

Length of output: 10678


🏁 Script executed:

#!/bin/bash
set -u

# Capture python3 exit code when bootstrap helper is missing
set +e
python3 "/tmp/cc-task-gate-bootstrap.py" >/dev/null 2>&1
rc=$?
set -e
echo "python3_missing_bootstrap_exit_code=$rc"

# Re-check canonical deploy closure: hapax-post-merge-deploy source list
rg -n --no-heading 'canonical gate closure|GATE_STAGE|cc-task-gate\.impl\.sh|agent-role\.sh|escape-grant\.sh|hooks-doctor\.sh' scripts/hapax-post-merge-deploy

# Re-check hooks-doctor deploy_canonical copy list (what makes it into $HAPAX_CANONICAL_HOOKS)
rg -n --no-heading 'deploy_canonical\(\)|install -m|for s in agent-role\.sh escape-grant\.sh hooks-doctor\.sh|CANONICAL_DIR' hooks/scripts/hooks-doctor.sh

Repository: hapax-systems/hapax-council

Length of output: 2519


Include cc-task-gate-bootstrap.py in the canonical gate deployment closure.
hooks/scripts/cc-task-gate.impl.sh runs the helper as python3 "$SCRIPT_DIR/cc-task-gate-bootstrap.py"; canonical deploy via hapax-post-merge-deploy + hooks/scripts/hooks-doctor.sh --deploy-canonical installs only the gate impl (as cc-task-gate.sh) plus agent-role.sh, escape-grant.sh, and hooks-doctor.sh—it does not copy cc-task-gate-bootstrap.py. When it’s missing, python3 exits 2, and the gate’s unclaimed bootstrap path exits 2 (fail every bootstrap-write attempt after leaving a repo checkout).

🤖 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 `@hooks/scripts/cc-task-gate.impl.sh` around lines 248 - 250, The canonical
deploy currently omits the helper script used by cc-task-gate.impl.sh, causing
python3 "$SCRIPT_DIR/cc-task-gate-bootstrap.py" to fail; update the canonical
deployment closure used by hapax-post-merge-deploy / hooks-doctor.sh
--deploy-canonical so that cc-task-gate-bootstrap.py is installed alongside
cc-task-gate.sh (and the other gate files), or alter the deploy manifest that
builds the closure to explicitly include cc-task-gate-bootstrap.py; ensure the
installed SCRIPT_DIR contains cc-task-gate-bootstrap.py so the
cc-task-gate.impl.sh bootstrap invocation succeeds.

Comment on lines +371 to +376
# Lease heartbeat (reform Phase 1, cluster 6): refresh the resolved claim file's
# mtime — this session is demonstrably alive (it is making a gated call against a
# valid claim). cc-claim treats a claim older than the lease TTL as free, so this
# keeps a live session's lease from aging out and being reaped while a dead
# session's lease still expires. Best-effort; never blocks the decision below.
touch "$claim_file" 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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't renew the lease until the claim is proven valid.

Lines 371-376 refresh the claim file before the task note is found and before assigned_to / status are checked. A stale, reassigned, or terminal claim can therefore stay alive indefinitely just by triggering blocked mutations, which defeats the lease-expiry cleanup this heartbeat is meant to support.

🤖 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 `@hooks/scripts/cc-task-gate.impl.sh` around lines 371 - 376, The lease
heartbeat currently touches "$claim_file" before the task note is located and
before validating assigned_to and status, allowing stale or reassigned claims to
be kept alive; move the touch call so it only runs after the code has found the
task note and confirmed the claim's assigned_to matches and status is
non-terminal, and keep it best-effort (2>/dev/null || true) so it doesn't block
decision logic—i.e., perform the validations (assigned_to/status) first, then
call touch "$claim_file" to refresh the lease only for a proven-valid claim.

Comment on lines +177 to +179
mkdir -p "$CANONICAL_DIR"
# impl deploys AS cc-task-gate.sh (the name shims + settings.json resolve to).
install -m 0755 "$src/cc-task-gate.impl.sh" "$CANONICAL_DIR/cc-task-gate.sh"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail deployment/fanout on write errors instead of reporting success.

Line 177, Line 179, Line 188, and Line 210 perform state-changing writes without checking success. Since the script is not running with set -e, failures can be silently ignored while printing successful deploy/shim messages.

Suggested hard-fail guards for mutating writes
-  mkdir -p "$CANONICAL_DIR"
+  mkdir -p "$CANONICAL_DIR" || { echo "deploy: failed to create $CANONICAL_DIR" >&2; return 1; }
   # impl deploys AS cc-task-gate.sh (the name shims + settings.json resolve to).
-  install -m 0755 "$src/cc-task-gate.impl.sh" "$CANONICAL_DIR/cc-task-gate.sh"
+  install -m 0755 "$src/cc-task-gate.impl.sh" "$CANONICAL_DIR/cc-task-gate.sh" \
+    || { echo "deploy: failed to install canonical gate impl" >&2; return 1; }
@@
-  ln -sf "$CANONICAL_DIR/hooks-doctor.sh" "$bindir/hapax-hooks-doctor"
+  ln -sf "$CANONICAL_DIR/hooks-doctor.sh" "$bindir/hapax-hooks-doctor" \
+    || { echo "deploy: failed to create hapax-hooks-doctor symlink" >&2; return 1; }
@@
-    install -m 0755 "$shim_src" "$gate"
+    install -m 0755 "$shim_src" "$gate" || { echo "fanout: failed to write $gate" >&2; return 1; }
     echo "shimmed  $gate"
     n=$((n + 1))

Also applies to: 188-189, 210-212

🤖 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 `@hooks/scripts/hooks-doctor.sh` around lines 177 - 179, The script currently
performs mutating operations (e.g., mkdir -p "$CANONICAL_DIR" and install -m
0755 "$src/cc-task-gate.impl.sh" "$CANONICAL_DIR/cc-task-gate.sh") and other
state-changing writes at the other noted locations (lines around 188-189 and
210-212) without checking their exit status; update each mutating command so
failures are detected and cause the script to fail the deployment/fanout instead
of printing success—for each command (mkdir, install and the other write/replace
operations referenced) check the command's exit code and on non-zero log an
error and exit non-zero (or use a strict error handler like set -e preceded by
explicit error messages) so writes cannot be silently ignored.

Comment on lines +181 to +183
for s in agent-role.sh escape-grant.sh hooks-doctor.sh; do
[[ -r "$src/$s" ]] && install -m 0755 "$src/$s" "$CANONICAL_DIR/$s"
done
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Refuse deploy if any gate-closure sibling is missing in source.

Line 181 currently treats closure siblings as optional. That can deploy a partial or mixed-version canonical closure when source inputs are incomplete.

Require full source closure before deploy
-  local s
-  for s in agent-role.sh escape-grant.sh hooks-doctor.sh; do
-    [[ -r "$src/$s" ]] && install -m 0755 "$src/$s" "$CANONICAL_DIR/$s"
-  done
+  local s
+  for s in agent-role.sh escape-grant.sh hooks-doctor.sh; do
+    [[ -r "$src/$s" ]] || { echo "deploy: source closure missing $src/$s" >&2; return 1; }
+    install -m 0755 "$src/$s" "$CANONICAL_DIR/$s" || { echo "deploy: failed to install $s" >&2; return 1; }
+  done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for s in agent-role.sh escape-grant.sh hooks-doctor.sh; do
[[ -r "$src/$s" ]] && install -m 0755 "$src/$s" "$CANONICAL_DIR/$s"
done
for s in agent-role.sh escape-grant.sh hooks-doctor.sh; do
[[ -r "$src/$s" ]] || { echo "deploy: source closure missing $src/$s" >&2; return 1; }
install -m 0755 "$src/$s" "$CANONICAL_DIR/$s" || { echo "deploy: failed to install $s" >&2; return 1; }
done
🤖 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 `@hooks/scripts/hooks-doctor.sh` around lines 181 - 183, The loop that installs
closure siblings (for s in agent-role.sh escape-grant.sh hooks-doctor.sh)
currently treats missing source files as optional; change it to require all
siblings exist and are readable in $src before proceeding: iterate the same
list, test [[ -r "$src/$s" ]] and if any check fails, print a clear error
mentioning the missing $s and exit 1 (refuse deploy); only when all checks pass
run the install -m 0755 "$src/$s" "$CANONICAL_DIR/$s" for each file.

Comment on lines +779 to +783
if [ -x "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" ]; then
bash "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" --deploy-canonical --from "$GATE_STAGE" \
|| echo "warning: canonical gate deploy failed" >&2
else
echo "warning: hooks-doctor.sh absent at $SHA; skipping canonical gate deploy" >&2
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not swallow canonical gate deploy failure in post-merge path.

Line 780-783 converts deploy errors into warnings and continues. That allows a “successful” post-merge deploy with stale canonical gate state.

Propagate deployment failure to caller
-    if [ -x "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" ]; then
-        bash "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" --deploy-canonical --from "$GATE_STAGE" \
-            || echo "warning: canonical gate deploy failed" >&2
-    else
-        echo "warning: hooks-doctor.sh absent at $SHA; skipping canonical gate deploy" >&2
-    fi
+    if [ -x "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" ]; then
+        bash "$GATE_STAGE/hooks/scripts/hooks-doctor.sh" --deploy-canonical --from "$GATE_STAGE"
+    else
+        echo "canonical gate deploy unavailable: hooks-doctor.sh absent at $SHA" >&2
+        rm -rf "$GATE_STAGE" 2>/dev/null || true
+        exit 1
+    fi
@@
-    DID_ANYTHING=1
+    DID_ANYTHING=1

Also applies to: 786-786

🤖 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 `@scripts/hapax-post-merge-deploy` around lines 779 - 783, The post-merge
deploy currently swallows failures by appending "|| echo ..." when invoking
hooks-doctor.sh; change that so any non-zero exit from the canonical gate deploy
propagates to the caller instead of being downgraded to a warning. Specifically,
in the block that checks executable "$GATE_STAGE/hooks/scripts/hooks-doctor.sh"
and in the other similar block near the later occurrence, replace the "bash ...
|| echo 'warning: canonical gate deploy failed' >&2" pattern with logic that
runs bash, captures its exit status, prints a meaningful error to stderr, and
then exits with that same non-zero status (or simply let the script exit
non-zero), ensuring failures bubble up to the caller.

# --session always exits 0 (never fail-stuck); --notify ntfy's when drift is
# present, catching a rebuild that clobbers the canonical gate (NEW-CATCH-3) or a
# lane that re-drifts between SessionStart/CI checks.
ExecStart=/bin/bash -c 'r="$HOME/projects/hapax-council"; for d in "$HOME/.local/lib/hapax/hooks" "$HOME/.cache/hapax/rebuild/worktree/hooks/scripts" "$r/hooks/scripts"; do if [ -x "$d/hooks-doctor.sh" ]; then exec bash "$d/hooks-doctor.sh" --session --notify --root "$r"; fi; done; echo "hooks-doctor.sh not found in any known location" >&2; exit 0'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not treat “hooks-doctor not found” as success.

Line 18 ends with exit 0 when no script is found, which silently disables drift sweeps while leaving the unit green.

Fail the unit when doctor script cannot be resolved
-ExecStart=/bin/bash -c 'r="$HOME/projects/hapax-council"; for d in "$HOME/.local/lib/hapax/hooks" "$HOME/.cache/hapax/rebuild/worktree/hooks/scripts" "$r/hooks/scripts"; do if [ -x "$d/hooks-doctor.sh" ]; then exec bash "$d/hooks-doctor.sh" --session --notify --root "$r"; fi; done; echo "hooks-doctor.sh not found in any known location" >&2; exit 0'
+ExecStart=/bin/bash -c 'r="$HOME/projects/hapax-council"; for d in "$HOME/.local/lib/hapax/hooks" "$HOME/.cache/hapax/rebuild/worktree/hooks/scripts" "$r/hooks/scripts"; do if [ -x "$d/hooks-doctor.sh" ]; then exec bash "$d/hooks-doctor.sh" --session --notify --root "$r"; fi; done; echo "hooks-doctor.sh not found in any known location" >&2; exit 1'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ExecStart=/bin/bash -c 'r="$HOME/projects/hapax-council"; for d in "$HOME/.local/lib/hapax/hooks" "$HOME/.cache/hapax/rebuild/worktree/hooks/scripts" "$r/hooks/scripts"; do if [ -x "$d/hooks-doctor.sh" ]; then exec bash "$d/hooks-doctor.sh" --session --notify --root "$r"; fi; done; echo "hooks-doctor.sh not found in any known location" >&2; exit 0'
ExecStart=/bin/bash -c 'r="$HOME/projects/hapax-council"; for d in "$HOME/.local/lib/hapax/hooks" "$HOME/.cache/hapax/rebuild/worktree/hooks/scripts" "$r/hooks/scripts"; do if [ -x "$d/hooks-doctor.sh" ]; then exec bash "$d/hooks-doctor.sh" --session --notify --root "$r"; fi; done; echo "hooks-doctor.sh not found in any known location" >&2; exit 1'
🤖 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 `@systemd/units/hapax-hooks-doctor.service` at line 18, The ExecStart shell
currently ends with `exit 0` when no hooks-doctor.sh is found, which treats
missing doctor script as success; update the hapax-hooks-doctor.service
ExecStart command so the final fallback exits non‑zero (e.g., `exit 1`) to make
the unit fail when hooks-doctor.sh cannot be resolved, keeping the rest of the
loop and search order for "$HOME/.local/lib/hapax/hooks",
"$HOME/.cache/hapax/rebuild/worktree/hooks/scripts", and
"$HOME/projects/hapax-council/hooks/scripts" intact and ensuring systemd marks
the unit failed instead of green.

Merged via the queue into main with commit 1651cde Jun 1, 2026
39 checks passed
@ryanklee ryanklee deleted the beta/reform-improve-gate-shim-collapse-20260601 branch June 1, 2026 14:59
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