fix(#1995): widen worktree branch regex to accept agent-<id> namespace (#2548)

* test(#1995): regression test for agent-<id> branch namespace

Add failing-first tests proving that normalizeCleanupManifestEntry and
planWorktreeRecordAgent reject Claude Code's current agent-<id> isolation
branches (only worktree-agent-<id> is accepted). Boundary tests cover both
namespaces plus rejection cases.

* fix(#1995): widen worktree branch regex to accept agent-<id> namespace

Claude Code's isolation="worktree" branch naming changed from
worktree-agent-<id> to agent-<id>. Widen the regex in all 7 locations
from ^worktree-agent-[A-Za-z0-9._/-]+$ to ^(worktree-)?agent-[A-Za-z0-9._/-]+$
so both namespaces are accepted. Introduce a shared WORKTREE_AGENT_BRANCH_RE
constant in src/worktree-safety.cts to prevent future drift.

Closes #1995

* fix(#1995): update workflow guards, test assertions, and baselines

Widen the branch-check regex in execute-phase.md and execute-plan.md.
Update all test assertions that checked for ^worktree-agent- to expect
the widened ^(worktree-)?agent- pattern. Regenerate golden-install-parity
fixtures, agent-size-baseline, and workflow-size-baseline.

Closes #1995

* fix(#1995): update extractCwdGuardBash sanity check for widened regex

The e2e test's sanity check verified the extracted bash block contained
'worktree-agent-'. After widening to '(worktree-)?agent-', update the
check to match the new pattern.

* fix(#1995): widen missed workflow-guard branch check + changeset + lint fixes

- hooks/gsd-workflow-guard.js: widen startsWith('worktree-agent-') to
  /^(worktree-)?agent-/ regex — same defect class, was missed in prior commit
- tests/worktree.test.cjs: fix indentation regression from prior edit
- Add .changeset/1995-worktree-agent-branch-namespace.md (pr:0 placeholder)

Found by orthogonal code review (Step 4).

* fix(#1995): regenerate golden + size baselines for workflow-guard change

* docs(#1995): backfill changeset PR number (2548)
This commit is contained in:
Tom Boucher
2026-07-23 07:36:53 -04:00
committed by GitHub
parent 4fc89497d0
commit 77bf21b3a6
33 changed files with 254 additions and 140 deletions

View File

@@ -187,10 +187,10 @@ describe('bug #2924: worktree HEAD attachment + destructive recovery', () => {
const scripts = codeBlocks.map(({ body }) => body).join('\n');
// Allow-list must reference the canonical Claude Code worktree-agent-<id>
// namespace via a regex assertion (grep -Eq '^worktree-agent-...').
const allowListRe = /grep\s+-Eq?\s+'\^worktree-agent-/;
const allowListRe = /grep\s+-Eq?\s+'\^\(worktree-\)\?agent-/;
assert.ok(
allowListRe.test(scripts),
'worktree_branch_check must enforce a positive allow-list matching ^worktree-agent-* (#2924 hardening)'
'worktree_branch_check must enforce a positive allow-list matching ^(worktree-)?agent-* (#2924 hardening)'
);
});
@@ -322,10 +322,10 @@ describe('bug #2924: worktree HEAD attachment + destructive recovery', () => {
});
test('block enforces positive worktree-agent-* allow-list (#2924 hardening)', () => {
const allowListRe = /grep\s+-Eq?\s+'\^worktree-agent-/;
const allowListRe = /grep\s+-Eq?\s+'\^\(worktree-\)\?agent-/;
assert.ok(
allowListRe.test(block),
'quick.md worktree_branch_check must enforce a positive allow-list matching ^worktree-agent-* (#2924 hardening)'
'quick.md worktree_branch_check must enforce a positive allow-list matching ^(worktree-)?agent-* (#2924 hardening)'
);
});
});
@@ -404,10 +404,10 @@ describe('bug #2924: worktree HEAD attachment + destructive recovery', () => {
test('step 0 enforces positive worktree-agent-* allow-list (#2924 hardening)', () => {
const codeBlocks = extractFencedCodeBlocks(block);
const scripts = codeBlocks.map(({ body }) => body).join('\n');
const allowListRe = /grep\s+-Eq?\s+'\^worktree-agent-/;
const allowListRe = /grep\s+-Eq?\s+'\^\(worktree-\)\?agent-/;
assert.ok(
allowListRe.test(scripts),
'task_commit_protocol step 0 must enforce a positive allow-list matching ^worktree-agent-* in addition to the protected-ref deny-list (#2924 hardening)'
'task_commit_protocol step 0 must enforce a positive allow-list matching ^(worktree-)?agent-* in addition to the protected-ref deny-list (#2924 hardening)'
);
});
});
@@ -1531,8 +1531,8 @@ function extractCwdGuardBash() {
if (!guardBash.includes('git rev-parse --show-toplevel')) {
throw new Error('extractCwdGuardBash: sanity check failed — extracted block does not contain "git rev-parse --show-toplevel"');
}
if (!guardBash.includes('worktree-agent-')) {
throw new Error('extractCwdGuardBash: sanity check failed — extracted block does not contain "worktree-agent-"');
if (!guardBash.includes('(worktree-)?agent-')) {
throw new Error('extractCwdGuardBash: sanity check failed — extracted block does not contain "(worktree-)?agent-"');
}
return guardBash;