fix(#3021): recognize worktree-wf_* branch namespace in all guards (#3109)

* fix(#3021): recognize worktree-wf_* branch namespace in all guards

The Claude-orchestration Workflow backend (#1143) creates per-plan
worktrees on branches named worktree-wf_<runid>-<n>. Four independent
copies of the agent branch allow-list regex (^(worktree-)?agent-...) never
learned this namespace:
- hooks/gsd-worktree-path-guard.js:176 — FAILED OPEN (process.exit(0)),
  silently disabling path containment for exactly the concurrent dispatch
  mode where cross-worktree writes are most likely
- src/worktree-safety.cts:21 — silently dropped cleanup-wave manifest
  entries
- agents/gsd-executor.md:503 — FATAL halt on branch check
- gsd-core/references/worktree-branch-check.md:33 — same FATAL halt

Extended all four to ^((worktree-)?agent-|worktree-wf_)[A-Za-z0-9._/-]+$.
The path guard now correctly blocks cross-worktree writes for Workflow-
backend branches instead of no-op'ing.

* chore(#3021): backfill changeset PR number 3109

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-06 04:26:59 -04:00
committed by GitHub
parent bbdf34a332
commit 10da377794
11 changed files with 36 additions and 22 deletions

View File

@@ -18,7 +18,10 @@ import { execGit as execGitSeam, posixNormalize, type SpawnResultOutput } from '
// remote, stalled NFS mount, etc.). Callers can override via deps.timeout.
const DEFAULT_GIT_TIMEOUT_MS = 10000;
const WORKTREE_AGENT_BRANCH_RE = /^(worktree-)?agent-[A-Za-z0-9._/-]+$/;
// #3021: accept the Workflow tool's worktree-wf_<runid>-<n> naming convention
// (claude-orchestration's isolation:"worktree" emission) alongside the
// existing agent-<id> / worktree-agent-<id> shapes.
const WORKTREE_AGENT_BRANCH_RE = /^((worktree-)?agent-|worktree-wf_)[A-Za-z0-9._/-]+$/;
const WORKTREE_AGENT_BRANCH_PATTERN = WORKTREE_AGENT_BRANCH_RE.source;
// GitResult now aliases the canonical SpawnResultOutput (shell-command-projection.cts),
@@ -1026,7 +1029,7 @@ function planWorktreeRecordAgent(manifestRaw: string, fields: RecordAgentFields)
return {
ok: false,
reason: 'invalid_entry',
hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ${WORKTREE_AGENT_BRANCH_PATTERN} (accepts both agent-<id> and worktree-agent-<id> namespaces; got branch="${branch}"). Fix the field and re-run.`,
hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ${WORKTREE_AGENT_BRANCH_PATTERN} (accepts agent-<id>, worktree-agent-<id>, and worktree-wf_<runid> namespaces; got branch="${branch}"). Fix the field and re-run.`,
entry: null,
manifest: null,
};
@@ -1252,7 +1255,7 @@ function planWorktreeCreate(fields: WorktreeCreateFields): WorktreeCreatePlan {
return {
ok: false,
reason: 'invalid_entry',
hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ${WORKTREE_AGENT_BRANCH_PATTERN} (accepts both agent-<id> and worktree-agent-<id> namespaces; got branch="${branch}"). Fix the field and re-run.`,
hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ${WORKTREE_AGENT_BRANCH_PATTERN} (accepts agent-<id>, worktree-agent-<id>, and worktree-wf_<runid> namespaces; got branch="${branch}"). Fix the field and re-run.`,
entry: null,
};
}