Reject placeholder tokens when parsing branches

A message quoting a template command — `git checkout -b <branch>` —
parsed the literal "<branch>" as the chat's branch and the work panel
chip displayed it. Every command-parsed candidate now has to look like
a real git ref (alphanumeric start, ref charset, not a sha); the prose
patterns were already restricted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
klopez4212
2026-07-07 07:51:58 +01:00
co-authored by Claude Fable 5
parent f682796d43
commit d2d26bc07c
2 changed files with 33 additions and 4 deletions
@@ -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 <branch>"), null);
assert.equal(parseBranchFromCommand("git switch <name>"), null);
assert.equal(parseBranchFromCommand("git worktree add ../wt <branch>"), null);
assert.equal(parseBranchFromCommand("git worktree add ../<dir>"), null);
assert.equal(
deriveBranchFromAgentMessages(
[
{
pubkey: "cd".repeat(32),
content: "Run `git checkout -b <branch>` to start.",
},
],
"cd".repeat(32),
),
null,
);
});
@@ -123,12 +123,13 @@ function parseWorktreeAdd(args: string[]): string | null {
if (positional.length >= 2) {
// `git worktree add <path> <branch>` — 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 <path>` 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 <branch>` in an explanatory message must never surface
* "<branch>" 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);
}