Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/loopover-engine/src/miner/worktree-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ const MAX_SLUG_LENGTH = 64;

/** Deterministically slugify an attempt id into a filesystem- and git-ref-safe token (same id → same slug). */
function slugifyAttemptId(attemptId: string): string {
// Trim leading/trailing `-`/`.` both before and after truncation (#7528): `.` is allowed mid-slug, so a
// pre-trim-only pass misses the case where slice(0, MAX) lands on a literal `.`/`-` and leaves a
// git-ref-invalid trailing separator (git check-ref-format rejects refs ending in `.`).
const slug = attemptId
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "-")
.replace(/^[-.]+|[-.]+$/g, "")
.slice(0, MAX_SLUG_LENGTH)
.replace(/^[-.]+|[-.]+$/g, "");
if (!slug) throw new Error("invalid_attempt_id");
return slug.slice(0, MAX_SLUG_LENGTH);
return slug;
}

/**
Expand Down
24 changes: 24 additions & 0 deletions test/unit/worktree-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,32 @@ describe("planWorktree (#4269)", () => {
expect(long.branchName).toBe(`${WORKTREE_BRANCH_PREFIX}${"x".repeat(64)}`);
});

it("strips a trailing '.' produced by truncating at a mid-slug dot (#7528)", () => {
// 63 safe chars + '.' + more content past the cap → pre-trim keeps the dot (not at the edge),
// slice(0, 64) ends in '.', and the post-truncation trim must remove it for git ref-format.
const attemptId = `${"a".repeat(63)}.${"b".repeat(10)}`;
const plan = planWorktree({ repoPath: "/repo", attemptId });
expect(plan.branchName).toBe(`${WORKTREE_BRANCH_PREFIX}${"a".repeat(63)}`);
expect(plan.branchName.endsWith(".")).toBe(false);
expect(plan.branchName.endsWith("-")).toBe(false);
});

it("strips a trailing '-' produced by truncating at a mid-slug hyphen (#7528)", () => {
const attemptId = `${"a".repeat(63)}-${"b".repeat(10)}`;
const plan = planWorktree({ repoPath: "/repo", attemptId });
expect(plan.branchName).toBe(`${WORKTREE_BRANCH_PREFIX}${"a".repeat(63)}`);
expect(plan.branchName.endsWith("-")).toBe(false);
});

it("leaves a clean truncation boundary untouched when the 64th char is not a separator (#7528)", () => {
const attemptId = `${"a".repeat(64)}${"b".repeat(10)}`;
const plan = planWorktree({ repoPath: "/repo", attemptId });
expect(plan.branchName).toBe(`${WORKTREE_BRANCH_PREFIX}${"a".repeat(64)}`);
});

it("rejects an attempt id that sanitizes to nothing", () => {
expect(() => planWorktree({ repoPath: "/repo", attemptId: " --- " })).toThrow(/invalid_attempt_id/);
expect(() => planWorktree({ repoPath: "/repo", attemptId: "..." })).toThrow(/invalid_attempt_id/);
});
});

Expand Down