mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-22 03:18:54 +08:00
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:
@@ -72,6 +72,21 @@ export function applyBranchTemplate(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapses empty path segments produced when a template variable resolves to
|
||||||
|
* an empty string. For example, an issue title with no alphanumeric characters
|
||||||
|
* (emoji-only, CJK-only, or punctuation-only) makes `{{description}}` empty, so
|
||||||
|
* a template like `{{prefix}}{{description}}/{{entityNumber}}` yields
|
||||||
|
* `claude//123`. Consecutive slashes — and a leading or trailing slash — are
|
||||||
|
* rejected by `validateBranchName`, which aborts the whole run, so normalize
|
||||||
|
* them into a valid branch name instead of crashing.
|
||||||
|
*/
|
||||||
|
function collapseEmptyPathSegments(branchName: string): string {
|
||||||
|
return branchName
|
||||||
|
.replace(/\/{2,}/g, "/") // collapse runs of slashes left by empty segments
|
||||||
|
.replace(/^\/+|\/+$/g, ""); // drop leading/trailing slashes
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a branch name from the provided `template` and set of `variables`. Uses a default format if the template is empty or produces an empty result.
|
* Generates a branch name from the provided `template` and set of `variables`. Uses a default format if the template is empty or produces an empty result.
|
||||||
*/
|
*/
|
||||||
@@ -97,7 +112,9 @@ export function generateBranchName(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (template?.trim()) {
|
if (template?.trim()) {
|
||||||
const branchName = applyBranchTemplate(template, variables);
|
const branchName = collapseEmptyPathSegments(
|
||||||
|
applyBranchTemplate(template, variables),
|
||||||
|
);
|
||||||
|
|
||||||
// Some templates could produce empty results- validate
|
// Some templates could produce empty results- validate
|
||||||
if (branchName.trim().length > 0) return branchName;
|
if (branchName.trim().length > 0) return branchName;
|
||||||
|
|||||||
@@ -291,5 +291,68 @@ describe("branch template utilities", () => {
|
|||||||
expect(result).toMatch(/^fix\/pr-456-\d{8}-\d{4}$/);
|
expect(result).toMatch(/^fix\/pr-456-\d{8}-\d{4}$/);
|
||||||
expect(result.length).toBeLessThanOrEqual(50);
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user