diff --git a/desktop/src/features/chats/lib/chatWorkBranch.test.mjs b/desktop/src/features/chats/lib/chatWorkBranch.test.mjs index cecd8b545..e92de836a 100644 --- a/desktop/src/features/chats/lib/chatWorkBranch.test.mjs +++ b/desktop/src/features/chats/lib/chatWorkBranch.test.mjs @@ -141,3 +141,22 @@ test("non-agent messages and branchless text derive nothing", () => { ); assert.equal(deriveBranchFromAgentMessages([], null), null); }); + +test("placeholder tokens in template commands never parse as branches", () => { + assert.equal(parseBranchFromCommand("git checkout -b "), null); + assert.equal(parseBranchFromCommand("git switch "), null); + assert.equal(parseBranchFromCommand("git worktree add ../wt "), null); + assert.equal(parseBranchFromCommand("git worktree add ../"), null); + assert.equal( + deriveBranchFromAgentMessages( + [ + { + pubkey: "cd".repeat(32), + content: "Run `git checkout -b ` to start.", + }, + ], + "cd".repeat(32), + ), + null, + ); +}); diff --git a/desktop/src/features/chats/lib/chatWorkBranch.ts b/desktop/src/features/chats/lib/chatWorkBranch.ts index 1226b7d27..cae1b2a18 100644 --- a/desktop/src/features/chats/lib/chatWorkBranch.ts +++ b/desktop/src/features/chats/lib/chatWorkBranch.ts @@ -123,12 +123,13 @@ function parseWorktreeAdd(args: string[]): string | null { if (positional.length >= 2) { // `git worktree add ` — a commit-ish second arg (sha) // isn't a branch name worth showing. - return looksLikeSha(positional[1]) ? null : positional[1]; + return isPlausibleBranchName(positional[1]) ? positional[1] : null; } if (positional.length === 1) { // `git worktree add ` creates a branch named after the basename. const parts = positional[0].split("/").filter(Boolean); - return parts[parts.length - 1] || null; + const basename = parts[parts.length - 1] ?? ""; + return isPlausibleBranchName(basename) ? basename : null; } return null; } @@ -145,7 +146,7 @@ function parseCheckoutOrSwitch(args: string[]): string | null { return null; } const positional = args.filter((token) => !token.startsWith("-")); - if (positional.length !== 1 || looksLikeSha(positional[0])) { + if (positional.length !== 1 || !isPlausibleBranchName(positional[0])) { return null; } return positional[0]; @@ -155,7 +156,7 @@ function valueOfFlag(args: string[], flags: string[]): string | null { for (let index = 0; index < args.length; index += 1) { if (flags.includes(args[index])) { const value = args[index + 1]; - if (value && !value.startsWith("-")) { + if (value && isPlausibleBranchName(value)) { return value; } } @@ -163,6 +164,15 @@ function valueOfFlag(args: string[], flags: string[]): string | null { return null; } +/** + * Real git ref charset, no placeholders: template text like + * `git checkout -b ` in an explanatory message must never surface + * "" as the chip's branch. + */ +function isPlausibleBranchName(token: string): boolean { + return /^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(token) && !looksLikeSha(token); +} + function looksLikeSha(token: string): boolean { return /^[0-9a-f]{7,40}$/i.test(token); }