From 6ee201f0231fbb762d22664f12aad2e7ae0401ae Mon Sep 17 00:00:00 2001 From: Naoyoshi Aikawa Date: Thu, 23 Apr 2026 14:17:44 +0900 Subject: [PATCH] fix: allow + in branch names (generated by Claude Code EnterWorktree) (#1248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code's EnterWorktree tool converts "/" to "+" when generating branch names from worktree names (e.g. EnterWorktree("feat/foo") creates branch "worktree-feat+foo"). The strict whitelist in validateBranchName rejected these names, causing claude-code-action to fail on any PR opened from an EnterWorktree-generated branch. Since all git calls use execFileSync (not shell interpolation), "+" carries no command injection risk — the same rationale used for allowing "#". Git itself permits "+" in branch names per git-check-ref-format. Fixes: https://github.com/anthropics/claude-code-action/issues/1244 Co-authored-by: Claude Sonnet 4.6 --- src/github/operations/branch.ts | 10 ++++++---- test/validate-branch-name.test.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/github/operations/branch.ts b/src/github/operations/branch.ts index 4de3546..870cd64 100644 --- a/src/github/operations/branch.ts +++ b/src/github/operations/branch.ts @@ -58,14 +58,16 @@ export function validateBranchName(branchName: string): void { ); } - // Strict whitelist pattern: alphanumeric start, then alphanumeric/slash/hyphen/underscore/period/hash. + // Strict whitelist pattern: alphanumeric start, then alphanumeric/slash/hyphen/underscore/period/hash/plus. // # is valid per git-check-ref-format and commonly used in branch names like "fix/#123-description". - // All git calls use execFileSync (not shell interpolation), so # carries no injection risk. - const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9/_.#-]*$/; + // + is valid per git-check-ref-format and generated by Claude Code's EnterWorktree tool when + // converting worktree names containing "/" (e.g. "feat/foo" becomes "worktree-feat+foo"). + // All git calls use execFileSync (not shell interpolation), so neither # nor + carries injection risk. + const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9/_.#+-]*$/; if (!validPattern.test(branchName)) { throw new Error( - `Invalid branch name: "${branchName}". Branch names must start with an alphanumeric character and contain only alphanumeric characters, forward slashes, hyphens, underscores, periods, or hashes (#).`, + `Invalid branch name: "${branchName}". Branch names must start with an alphanumeric character and contain only alphanumeric characters, forward slashes, hyphens, underscores, periods, hashes (#), or plus signs (+).`, ); } diff --git a/test/validate-branch-name.test.ts b/test/validate-branch-name.test.ts index 7fed15e..9594b48 100644 --- a/test/validate-branch-name.test.ts +++ b/test/validate-branch-name.test.ts @@ -45,6 +45,16 @@ describe("validateBranchName", () => { ).not.toThrow(); expect(() => validateBranchName("fix/issue-#42")).not.toThrow(); }); + + it("should accept branch names containing + (generated by Claude Code EnterWorktree)", () => { + // EnterWorktree converts "/" in worktree names to "+" when generating branch names. + // e.g. EnterWorktree("feat/skill-consolidation") → branch "worktree-feat+skill-consolidation" + expect(() => + validateBranchName("worktree-feat+skill-consolidation"), + ).not.toThrow(); + expect(() => validateBranchName("fix+issue-123")).not.toThrow(); + expect(() => validateBranchName("feature+new-thing")).not.toThrow(); + }); }); describe("command injection attempts", () => {