You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
packages/loopover-engine/src/miner/worktree-plan.ts's module header (lines 9-11) makes an explicit
no-collision guarantee:
"naming is deterministic and keyed on the attempt id (never a random suffix), so two concurrent
attempts on the same repo can never be handed the same worktree path or branch"
planWorktree derives both worktreePath and branchName solely from slugifyAttemptId (lines
38-51):
Lowercasing and collapsing any run of non-[a-z0-9._-] characters to a single - means genuinely
distinct attempt ids can collide on the same slug: "Attempt-ABC" and "attempt-abc" both produce "attempt-abc"; "retry:42" and "retry!42" both produce "retry-42". Since the slug is the sole
input to both the worktree path and the branch name, two such attempts would be handed the identical
worktree/branch — directly contradicting the module's own guarantee.
attemptId is caller-suppliable, not internally generated: packages/loopover-miner/lib/attempt-cli.ts
line 406 reads options.attemptId ?? ${parsed.repoFullName.replace("/", "_")}-${parsed.issueNumber}-${nowMs}``
— an operator or an automated caller can pass an explicit attemptId, making this reachable from the
real CLI, not just a theoretical input. The would-be collision guard in `worktree-pool.ts`,
`isWorktreeAllocated`, checks the raw `attemptId` string, not the slug, so it does not catch this
class of collision either.
test/unit/worktree-plan.test.ts's "no collision" assertion (lines 24-32) only compares "attempt-42" vs "attempt-43" — two slugs that were already distinct before slugification — so it
never exercises a case/punctuation-collapsing collision.
Requirements
Change planWorktree (or slugifyAttemptId) so that two attempt ids which are distinct strings
but collapse to the same slug under the current sanitization NEVER produce the same worktreePath/branchName. The fix must preserve slugifyAttemptId's existing properties: it is
still deterministic (same attemptId in → same output every time), the result is still
filesystem- and git-ref-safe, and the existing truncation/trim-trailing-separator behavior for the MAX_SLUG_LENGTH case (fix(engine): slugifyAttemptId's trim-then-truncate order can produce a git-invalid worktree branch name ending in "." #7528) is unchanged for ids that don't collide.
A reasonable approach is appending a short deterministic disambiguator derived from the original, pre-sanitizationattemptId (e.g. a short hash of the raw string) whenever the
sanitized slug differs from a case-normalized/character-collapsed reduction of the input — but the
exact mechanism is up to the implementation as long as it satisfies the collision-free requirement
above and the existing MAX_SLUG_LENGTH cap is still respected after any disambiguator is
appended.
Do not change WorktreeExecFn, addWorktree, removeWorktree, or the retention-policy behavior
in this file — this issue is scoped to the naming/collision logic only.
Deliverables
planWorktree({ repoPath: "/repo", attemptId: "Attempt-ABC" }) and planWorktree({ repoPath: "/repo", attemptId: "attempt-abc" }) produce different worktreePath and branchName values.
planWorktree({ repoPath: "/repo", attemptId: "retry:42" }) and planWorktree({ repoPath: "/repo", attemptId: "retry!42" }) produce different worktreePath
and branchName values.
The same attemptId passed twice still produces the identical plan (determinism is
preserved).
All four Deliverables are required in this one PR — there is no narrower scope for this issue.
Test Coverage Requirements
packages/loopover-engine/src/** is measured by Codecov via two separate uploads whose hits are
unioned — root test/** AND packages/loopover-engine/test/**. This module's existing test lives
at root test/unit/worktree-plan.test.ts — add the new collision-regression tests there (add packages/loopover-engine/test/** coverage too if the fix touches any code path not already
exercised from root). Assert each Deliverable above as an explicit test case. Target 100% branch
coverage of any new disambiguation logic.
Expected Outcome
worktree-plan.ts's documented no-collision guarantee holds for attempt ids that differ only in
case or punctuation, not just for ids that were already distinct after sanitization.
Context
packages/loopover-engine/src/miner/worktree-plan.ts's module header (lines 9-11) makes an explicitno-collision guarantee:
planWorktreederives bothworktreePathandbranchNamesolely fromslugifyAttemptId(lines38-51):
Lowercasing and collapsing any run of non-
[a-z0-9._-]characters to a single-means genuinelydistinct attempt ids can collide on the same slug:
"Attempt-ABC"and"attempt-abc"both produce"attempt-abc";"retry:42"and"retry!42"both produce"retry-42". Since the slug is the soleinput to both the worktree path and the branch name, two such attempts would be handed the identical
worktree/branch — directly contradicting the module's own guarantee.
attemptIdis caller-suppliable, not internally generated:packages/loopover-miner/lib/attempt-cli.tsline 406 reads
options.attemptId ??${parsed.repoFullName.replace("/", "_")}-${parsed.issueNumber}-${nowMs}``— an operator or an automated caller can pass an explicit
attemptId, making this reachable from thereal CLI, not just a theoretical input. The would-be collision guard in `worktree-pool.ts`,
`isWorktreeAllocated`, checks the raw `attemptId` string, not the slug, so it does not catch this
class of collision either.
test/unit/worktree-plan.test.ts's "no collision" assertion (lines 24-32) only compares"attempt-42"vs"attempt-43"— two slugs that were already distinct before slugification — so itnever exercises a case/punctuation-collapsing collision.
Requirements
planWorktree(orslugifyAttemptId) so that two attempt ids which are distinct stringsbut collapse to the same slug under the current sanitization NEVER produce the same
worktreePath/branchName. The fix must preserveslugifyAttemptId's existing properties: it isstill deterministic (same
attemptIdin → same output every time), the result is stillfilesystem- and git-ref-safe, and the existing truncation/trim-trailing-separator behavior for the
MAX_SLUG_LENGTHcase (fix(engine): slugifyAttemptId's trim-then-truncate order can produce a git-invalid worktree branch name ending in "." #7528) is unchanged for ids that don't collide.original, pre-sanitization
attemptId(e.g. a short hash of the raw string) whenever thesanitized slug differs from a case-normalized/character-collapsed reduction of the input — but the
exact mechanism is up to the implementation as long as it satisfies the collision-free requirement
above and the existing
MAX_SLUG_LENGTHcap is still respected after any disambiguator isappended.
WorktreeExecFn,addWorktree,removeWorktree, or the retention-policy behaviorin this file — this issue is scoped to the naming/collision logic only.
Deliverables
planWorktree({ repoPath: "/repo", attemptId: "Attempt-ABC" })andplanWorktree({ repoPath: "/repo", attemptId: "attempt-abc" })produce differentworktreePathandbranchNamevalues.planWorktree({ repoPath: "/repo", attemptId: "retry:42" })andplanWorktree({ repoPath: "/repo", attemptId: "retry!42" })produce differentworktreePathand
branchNamevalues.attemptIdpassed twice still produces the identical plan (determinism ispreserved).
MAX_SLUG_LENGTH/trailing-separator-trim behavior (fix(engine): slugifyAttemptId's trim-then-truncate order can produce a git-invalid worktree branch name ending in "." #7528) still holds for idsthat do not collide with anything.
All four Deliverables are required in this one PR — there is no narrower scope for this issue.
Test Coverage Requirements
packages/loopover-engine/src/**is measured by Codecov via two separate uploads whose hits areunioned — root
test/**ANDpackages/loopover-engine/test/**. This module's existing test livesat root
test/unit/worktree-plan.test.ts— add the new collision-regression tests there (addpackages/loopover-engine/test/**coverage too if the fix touches any code path not alreadyexercised from root). Assert each Deliverable above as an explicit test case. Target 100% branch
coverage of any new disambiguation logic.
Expected Outcome
worktree-plan.ts's documented no-collision guarantee holds for attempt ids that differ only incase or punctuation, not just for ids that were already distinct after sanitization.
Links & Resources
packages/loopover-engine/src/miner/worktree-plan.ts(module header lines 9-11,slugifyAttemptIdlines 38-51,
planWorktree)packages/loopover-miner/lib/attempt-cli.ts(line ~406, the real caller supplyingattemptId)test/unit/worktree-plan.test.ts