fix(branch): collapse empty path segments in branch_name_template (#1539)

A branch_name_template that places {{description}} (or another variable)
next to a slash crashes the run when the variable resolves to an empty
string. An issue/PR title with no ASCII-alphanumeric content — emoji-only,
CJK-only, or punctuation-only — makes extractDescription() return "", so a
template like "{{prefix}}{{description}}/{{entityNumber}}" produces
"claude//123". validateBranchName rejects consecutive (and leading/trailing)
slashes, and the thrown error propagates uncaught out of setupBranch,
aborting the entire run.

Normalize the templated result before the empty-result check: collapse runs
of slashes and drop any leading/trailing slash. Single-slash and dash
separators are untouched, so existing template behavior is unchanged; a
template that collapses to empty still falls back to the default format.

This is distinct from the {{label}} sanitization tracked in #1491 (and its
open PRs), which deliberately leave {{description}} alone — so this path
remained broken. Fixes the whole empty-segment class regardless of variable.

Adds regression tests for emoji-only and CJK-only titles, a trailing empty
segment, and a direct validateBranchName assertion proving the run no longer
aborts.
This commit is contained in:
NickNojiri
2026-08-07 07:59:52 -07:00
committed by GitHub
parent b704dd3960
commit 751e003832
2 changed files with 81 additions and 1 deletions
+63
View File
@@ -291,5 +291,68 @@ describe("branch template utilities", () => {
expect(result).toMatch(/^fix\/pr-456-\d{8}-\d{4}$/);
expect(result.length).toBeLessThanOrEqual(50);
});
// Regression: a title with no ASCII-alphanumeric content makes
// {{description}} sanitize to an empty string. Around a slash separator this
// previously produced "claude//123", which validateBranchName rejects
// ("cannot contain consecutive slashes"), aborting the entire run.
it("should collapse the double slash from an empty description (emoji-only title)", () => {
const template = "{{prefix}}{{description}}/{{entityNumber}}";
const result = generateBranchName(
template,
"claude/",
"issue",
123,
undefined,
undefined,
"🎉🎉🎉",
);
expect(result).toBe("claude/123");
});
it("should collapse the double slash for a CJK-only title", () => {
const template = "{{prefix}}{{description}}/{{entityNumber}}";
const result = generateBranchName(
template,
"claude/",
"issue",
123,
undefined,
undefined,
"日本語のタイトル",
);
expect(result).toBe("claude/123");
});
it("should drop a trailing slash left by an empty trailing segment", () => {
const template = "{{prefix}}{{entityNumber}}/{{description}}";
const result = generateBranchName(
template,
"claude/",
"issue",
123,
undefined,
undefined,
"!!! ???",
);
expect(result).toBe("claude/123");
});
it("should produce a name that passes validateBranchName when a segment is empty", () => {
const result = generateBranchName(
"{{prefix}}{{description}}/{{entityNumber}}",
"claude/",
"issue",
123,
undefined,
undefined,
"🎉",
);
expect(() => validateBranchName(result)).not.toThrow();
});
});
});