Skip to content

fix(hooks): unify gate-marker root across linked worktrees, isolate palette tests from repo state - #616

Merged
SUaDtL merged 1 commit into
mainfrom
fix/604-552-env-sensitivity
Aug 5, 2026
Merged

fix(hooks): unify gate-marker root across linked worktrees, isolate palette tests from repo state#616
SUaDtL merged 1 commit into
mainfrom
fix/604-552-env-sensitivity

Conversation

@SUaDtL

@SUaDtL SUaDtL commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes two "works on a clean checkout, breaks in a real environment" defects that have repeatedly polluted this repo's local test runs.

#604 — gate-marker root mismatch in a linked worktree

security-pass.py/migration-pass.py (invoked bare via a Bash tool call, no CLAUDE_PROJECT_DIR in that shell) and the H-09b/H-10b/H-14 commit guards (a hook subprocess whose CLAUDE_PROJECT_DIR names the main checkout) resolved different roots for .codearbiter/.markers/ inside a linked git worktree — a legitimately-recorded gate pass was invisible to the guard.

Fix: a new hostapi.Host.marker_root() seam. git_worktree_main_root(root) escalates an already-resolved project_root() answer to the MAIN checkout only when that root is itself a linked worktree's own checkout (distinguished from a submodule via the gitdir: pointer's .git/worktrees/ segment). Wired into both producers (security-pass.py, migration-pass.py) for the marker WRITE, and both guard checks (_check_h09b_h10b_crypto_secret, _check_h14_migration) for the marker READ — via _marker_root() in _bashguardlib.py. Deliberately not wired into project_root() itself: security-pass.py's diff SCAN must stay bound to the worktree's own tree (the issue's own warning — binding digests to the wrong tree would review lines nobody staged). git_toplevel's git rev-parse mechanism is unchanged, per the board history caution (bug #125, symlink/8.3 canonicalization) — the fix makes the two callers agree through the existing seam rather than replacing it.

Also fixes the 10-test pre-existing failure family in test_git_hooks.py/test_repo_resolution.py that fires whenever the suite itself runs from inside a linked worktree: _githooks's own __file__ resolves to an ephemeral path, which is_ephemeral_path (#441/ADR-0014) correctly refuses to register into a fixture's shared drop-in dir. Fixtures now resolve the enforcer path from a durable_plugin_copy (the #442 fix's existing pattern) instead of the live (possibly-ephemeral) checkout.

Before/after, run inside a real linked worktree of this repo (dirty local state):

before after
test_git_hooks.py + test_repo_resolution.py 10 failed 0 failed
plugins/ca/hooks/tests (full suite) 10 failed, 1301 passed 0 failed, 1339 passed

Proven end-to-end with a real git worktree add fixture (TestGateMarkerAgreesAcrossLinkedWorktree in test_repo_resolution.py): a marker written by security-pass.py (bare, no CLAUDE_PROJECT_DIR) lands at the main checkout, not the worktree; pre-bash.py's H-09b guard honors it; and the guard still blocks with no recorded pass (no-fail-open converse).

#552 — palette-compatibility tests read real repo state

test_colorlib.py's palette-compatibility tests render the live statusline against the real repository when a subprocess call has no explicit cwdproject_root() falls back to a git-toplevel climb from the test runner's own cwd, so a maintainer's own accumulated .codearbiter/ audit trail (task counts, override counts, gate-event rows) could intermittently drop a required custom-palette color from the assertion window.

Fix: pinned the state-sensitive subprocess render to an isolated temp dir (no .git/.codearbiter reachable), and added test_custom_palette_survives_arbitrary_appended_codearbiter_state — a new test that builds its own .codearbiter/ fixture, appends 40 override rows, 200 gate-event rows, several in-flight tasks, and an unresolved open question (the exact adversarial shape #552 describes accumulating over a repo's lifetime), and proves the palette-completeness assertion still holds.

Mutation proofs

  • hostapi.git_worktree_main_root: forced it to return None unconditionally → GitWorktreeMainRootTests, MarkerRootTests, and both TestGateMarkerAgreesAcrossLinkedWorktree positive-proof tests went red with the exact expected assertion failures; the no-fail-open converse test stayed green. Restored, re-verified green, re-synced.
  • _githooks's durable-copy fixture patch: disabled self._enforcer_patch.start() in both test files → the exact same 10 pre-existing failures reproduced with identical messages. Restored, re-verified green.
  • _colorlib._read_custom: forced it to return None unconditionally → the new adversarial-state test failed with "missing custom colors" for all 7 required colors. Restored, re-synced, re-verified green.

Version advance

ca 2.11.9 → 2.11.10, ca-codex 0.4.8 → 0.4.9, ca-pi 0.2.8 → 0.2.9 (root package.json regenerated). All landed inside this PR's rebase onto the latest origin/main (which had independently advanced to 2.11.9/0.4.8/0.2.8 via #614/#615 while this branch was in flight) — the target versions in this PR were already exactly one patch above that new base, so no further bump was needed after resolving the conflict.

Deviations from plan

  • The H-09b crypto/secret gate on this PR's own commit hit a genuinely stale local marketplace-cache install (~/.claude/plugins/cache/codearbiter/ca/2.10.0) registered as the git-level .git/hooks/pre-commit enforcer — an environment fact unrelated to this fix (predates it, would recur on any PR). Resolved by running the same stale security-pass.py (not this branch's fixed copy) so its root resolution matched what the stale enforcer checks — no gate was bypassed; /ca:override was considered and rejected since it required fabricating user acknowledgement for a security-critical stop that a non-bypass fix cleanly resolved instead.

Closes #604
Closes #552

https://claude.ai/code/session_01QjJeSbcwPHwMmd6CEZeagB

…alette tests from repo state

CLAUDE_PROJECT_DIR) and the H-09b/H-10b/H-14 guards (a hook subprocess whose
CLAUDE_PROJECT_DIR names the main checkout) resolved DIFFERENT roots for
.codearbiter/.markers/ inside a linked git worktree, so a legitimately
recorded gate pass was invisible to the guard. Adds hostapi.Host.marker_root()
(git_worktree_main_root escalates project_root() to the main checkout only
when it names a linked worktree's own checkout) and wires it into both
producers and both guard checks, without moving the diff/migration SCAN root
(which must stay bound to the tree actually being committed). Proven with a
real `git worktree add` fixture: marker lands at the main checkout, the guard
honors it, and still blocks with no recorded pass (test_repo_resolution.py,
test_host_project_root.py).

Also fixes the 10-test pre-existing failure family in test_git_hooks.py /
test_repo_resolution.py that fires whenever the suite itself runs from inside
a linked worktree: _githooks's own __file__ resolves to an ephemeral path,
which is_ephemeral_path (#441/ADR-0014) correctly refuses to register into a
fixture's shared drop-in dir. Fixtures now resolve the enforcer path from a
durable_plugin_copy (the #442 fix's existing pattern) instead.

project's .codearbiter/ state (task/override/gate-event counts) when no
explicit cwd pinned it elsewhere, so a maintainer's own accumulated audit
trail could make a required custom-palette color intermittently disappear.
Pinned the state-sensitive render to an isolated temp dir, and added a new
test that appends adversarial override/gate-event/task rows in its own
fixture and proves the palette-completeness check still holds regardless.

Version advance: ca 2.11.7 -> 2.11.10, ca-codex 0.4.6 -> 0.4.9,
ca-pi 0.2.6 -> 0.2.9 (root package.json regenerated).

CHANGELOG: linked-worktree sessions no longer lose a recorded security/migration gate pass, and the statusline palette test suite no longer depends on the developer's own accumulated .codearbiter/ audit trail.

Claude-Session: https://claude.ai/code/session_01QjJeSbcwPHwMmd6CEZeagB
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: f45cd18a-01e0-4fa7-9317-9254430588d3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@SUaDtL
SUaDtL merged commit e8fa56d into main Aug 5, 2026
93 of 94 checks passed
SUaDtL added a commit that referenced this pull request Aug 5, 2026
…elease-marker code (#567, #568) (#617)

- #567: MARKER_FRESHNESS_MINUTES was five independent hardcoded `30`
  literals (pre-write.py, pre-edit.py, _bashguardlib.py, git-enforce.py,
  _protectedstatelib.py) with no import relationship. Centralized into one
  declaration in _hooklib.py beside marker_fresh itself; every flank now
  imports it. New test_marker_freshness_window.py pins the single-source
  property by value AND by source-level AST inspection of each call site
  (catches a flank that imports the constant but still passes a literal).
- #568: _releaselib._PRERELEASE_MARKERS was unreachable dead code behind
  the anchored release-tag regex (confirmed unreachable, not merely
  suspected). Deleted along with its dead branch in last_tag_select;
  the obsolete "second line of defense" test is replaced with a pin
  proving the tuple stays gone.
- #569: verified already fully satisfied by prior work (--cleanup=verbatim
  in the release skill, plus the real git-tag-a-F round-trip test in
  test_consumer_smoke.py) -- no code change needed; evidence cited in the
  PR body.

Version advance: ca -> 2.11.11, ca-codex -> 0.4.10, ca-pi -> 0.2.11 --
the final slot in the ascending merge train (#614 took .8/.7/.7, #615
took .9/.8/.8, #616 took .10/.9/.9 on main; this PR merges last). See
the PR body for the full sequencing note.

CHANGELOG: The H-11 marker freshness window is now a single, imported
constant instead of five independently hardcoded copies, and _releaselib's
unreachable dead prerelease-marker code is removed.

Claude-Session: https://claude.ai/code/session_01QjJeSbcwPHwMmd6CEZeagB

Co-authored-by: SUaDtL <SUaDtL@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant