Skip to content

Commit a0fb3bf

Browse files
fix(release): ignore nested checkouts in the Quarkus alignment gate (#238)
`find_property()` scanned every `pom.xml` under the repo, excluding only `target` and `node_modules`. Agent tooling parks git worktrees in `.plan/local/worktrees/`, each a full copy of the project pinning whatever `version.quarkus` its branch carried — so the release gate reported a conflict that does not exist, and exited 2. Exit 2 is defined as blocking, so **any open worktree made the release gate unrunnable**, and a stale sibling branch could veto a release. The fix skips poms under a dot-directory, or under a directory carrying its own `.git` (a worktree has it as a file, a clone as a directory). Two rules because either alone leaves a hole: a worktree parked outside a dot-directory, or a dot-directory without `.git`. ## Verification Negative control — same repo, same simulated stale worktree, old code vs new: ``` BEFORE fix -> BLOCKED: property version.quarkus declared inconsistently: 3.30.0 (.plan/local/worktrees/__sim__/pom.xml), 3.39.1 (pom.xml) AFTER fix -> 3.39.1 (no veto) ``` The real gate on this branch: ``` ALIGNED (exit 0) ``` Behaviour checked against four cases: | case | result | |---|---| | genuine conflict between two real reactor modules | **still blocks** (positive control) | | worktree under a dot-directory | ignored | | nested checkout *not* hidden, with its own `.git` | ignored | | `target/` and `node_modules/` | ignored, as before | ## Sync `SKILL.md` says this script is kept in sync by hand across repos with no shared parent to inherit from. It exists in three places — `cuioss-parent-pom`, `TokenSheriff`, `API-Sheriff` — and all three were identical. TokenSheriff landed the fix in cuioss/TokenSheriff#678; this PR and its API-Sheriff/parent-pom counterpart carry the identical patch, so all three files stay byte-for-byte the same (verified by checksum). Bot review is skipped (`skip-bot-review`): a self-contained filter fix with the behaviour table above. Co-authored-by: Oliver Wolff <23139298+cuioss@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 30eb370 commit a0fb3bf

1 file changed

Lines changed: 26 additions & 1 deletion

File tree

‎.claude/skills/release/check-quarkus-alignment.py‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,37 @@ def _deref(value: str, props: dict[str, str]) -> str:
7272
return value
7373

7474

75+
def _outside_reactor(repo: Path, pom: Path) -> bool:
76+
"""True for a pom.xml that is on disk under ``repo`` but is not part of its reactor.
77+
78+
Build output (``target``) and vendored trees (``node_modules``) are the obvious cases.
79+
The subtle one is a *nested checkout*: agent tooling parks git worktrees inside the
80+
repository (``.plan/local/worktrees/<branch>/``), each a full copy of the project at
81+
some other revision. Their poms declare the same properties at whatever value that
82+
branch pinned, so scanning them turns every open worktree into a phantom
83+
"declared inconsistently" conflict and the check exits 2 -- which the release gate
84+
treats as blocking. A stale sibling branch must never be able to veto a release.
85+
86+
Two rules, because either alone leaves a hole: dot-directories (no reactor module
87+
lives in one) and any directory carrying its own ``.git`` (a worktree has it as a
88+
file, a clone as a directory) placed somewhere not hidden.
89+
"""
90+
if "target" in pom.parts or "node_modules" in pom.parts:
91+
return True
92+
for parent in pom.parents:
93+
if parent == repo or repo not in parent.parents:
94+
break
95+
if parent.name.startswith(".") or (parent / ".git").exists():
96+
return True
97+
return False
98+
99+
75100
def find_property(repo: Path, name: str) -> tuple[str, Path]:
76101
"""Find a property across the reactor. Later declarations do not override earlier
77102
ones silently -- a genuine conflict is an error, not a coin toss."""
78103
hits: list[tuple[str, Path]] = []
79104
for pom in sorted(repo.rglob("pom.xml")):
80-
if "target" in pom.parts or "node_modules" in pom.parts:
105+
if _outside_reactor(repo, pom):
81106
continue
82107
try:
83108
root = ET.parse(pom).getroot()

0 commit comments

Comments
 (0)