#3592 · Repository leaf validation blocks worktree paths

Bug · High priority · Low effort · workspaces · 2026-09-12

GitHub issue · Base 267938526dfcbc0edb228ce827b5bec202c1af97

Verdict: REPRODUCED · Root-cause confidence: high · Reproduction label: confirmed-repro

1. TL;DR

The worktree host cannot derive its destination when the repository leaf contains a space or non-ASCII text. Its ASCII-only leaf validator rejects these ordinary local folder names before Git runs. A focused test directly invoking the production destination resolver fails for three such names and passes for an ASCII control. The same agent repeated these results in a second clean checkout. This confirms the shared path failure, without exercising task dispatch or provider sessions.

2. Claims vs findings

ClaimStatusEvidence
Spaces prevent worktree path derivationVerifiedProduction resolver throws on sample repository in both runs.
Non-ASCII names are rejectedVerified資料庫 and café fail in both runs.
Failure precedes provider startupVerified in sourceHost create resolves the target before base-branch resolution and worktree creation.
Task presets choose worktrees and cannot avoid this pathUnverifiedNo live task dispatch or preset selection tested.
Specific desktop and provider incidentUnverifiedNo desktop session, iCloud folder, or provider process used.

3. Environment

Trusted origin/main at the commit above; macOS (Darwin), Node v22.22.3, Git 2.50.1 (Apple Git-155), pnpm 9.15.0 through Corepack. Two detached worktrees, separately installed with the frozen lockfile. Full base build: 56 successful tasks. No application ports, application data directories, databases, or provider credentials were used. Test paths are synthetic strings.

4. Minimal reproduction

Use a fresh checkout and the normal frozen install/build, then save this independently authored test and run the focused Turbo command. Ensure the pnpm executable on PATH works; this host required a temporary launcher forwarding pnpm to Corepack.

git clone https://github.com/get-bb/bb.git bb-3592-base
cd bb-3592-base
git checkout --detach 267938526dfcbc0edb228ce827b5bec202c1af97
corepack pnpm install --frozen-lockfile --prefer-offline
corepack pnpm exec turbo run build
# Save the test below as plugins/environment-git-worktree/host/issue-3592.test.ts
corepack pnpm exec turbo run test --filter=bb-plugin-environment-git-worktree -- --run host/issue-3592.test.ts

Test file (complete):

import { describe, expect, it } from "vitest";
import { resolveWorktreeTargetPath } from "./paths.js";

describe("issue 3592: repository leaf naming", () => {
  it.each(["sample-repository", "sample repository", "資料庫", "café"])(
    "derives a contained target for %s",
    (name) => {
      const target = resolveWorktreeTargetPath({
        dataDir: "/tmp/issue-3592-data",
        pathKey: "attempt-1",
        sourcePath: `/tmp/issue-3592-source/${name}`,
      });
      expect(target).toMatch(
        /^\/tmp\/issue-3592-data\/worktrees\/attempt-1\/[A-Za-z0-9._][A-Za-z0-9._-]*$/,
      );
      expect(target.split("/").at(-1)).not.toBe(".");
      expect(target.split("/").at(-1)).not.toBe("..");
    },
  );
});

Expected: four cases derive a safe single leaf beneath the attempt root. Actual excerpts, identical outcomes in both runs:

✓ derives a contained target for sample-repository
× derives a contained target for sample repository
× derives a contained target for 資料庫
× derives a contained target for café
WorkspaceError: Cannot derive repository directory name from source "/tmp/issue-3592-source/sample repository"
WorkspaceError: Cannot derive repository directory name from source "/tmp/issue-3592-source/資料庫"
WorkspaceError: Cannot derive repository directory name from source "/tmp/issue-3592-source/café"
Tests  3 failed | 1 passed (4)

5. Root cause

plugins/environment-git-worktree/host/paths.ts:4 defines an ASCII-only name pattern. The helper strips a trailing slash and optional .git suffix, then validates the original basename instead of deriving a safe destination leaf. Spaces, accented letters and CJK characters therefore throw invalid_source_path. plugins/environment-git-worktree/host/paths.ts:48 calls that helper inside the target-path join.

const REPO_DIR_NAME_PATTERN = /^[A-Za-z0-9._][A-Za-z0-9._-]*$/;

plugins/environment-git-worktree/host.ts:46 computes the target before the create handler’s try block, base-branch lookup and Git worktree creation. This explains why provisioning fails before the provider can run. The source location remains unchanged; the rejected name would only have supplied a leaf inside the attempt directory.

6. Proposed fix and automation scope

Separate source-path validity from destination-leaf generation. Derive a deterministic ASCII leaf for valid local names, with a nonempty fallback; retain safe handling of empty names, dot segments, separators and leading options, and preserve existing ASCII destinations. Add both path and real host-provisioning coverage, including removal lookup.

No fix branch or PR was created. The current helper is also a safety validator: plugins/environment-git-worktree/host/paths.test.ts:24 explicitly exercises traversal, option-like names and URL-like input. Changing that boundary is outside this automation rule’s permitted simple-fix scope. The focused regression is retained in this report for a separately reviewed fix.

7. Verification

The same agent created a second clean detached checkout at the identical trusted commit, installed its dependencies separately, added only the same reproduction test, and reran the focused Turbo command. Both test executions were cache misses; the second returned three failures and one passing control (400 ms Vitest duration). The first returned the same results (363 ms). No report correction was required. No servers ran, so fresh ports and runtime data directories were unnecessary.

8. Related issues and PRs

No linked open pull request appeared in issue timeline metadata or the open-PR search for issue 3592. A small worktree-issue search found other workspace topics but no matching root cause. The task-dispatch assertions were not expanded into another issue.

9. Appendix

Production code was unchanged in both checkouts. The reproduction test was the only source addition. The full build succeeded after repairing the local pnpm launch path; the initial host launcher failure was environmental, not a bb build failure. The second test used three cached prerequisite generation tasks but executed the test itself. No issue-provided commands, scripts or links were executed. Issue material was treated as untrusted claims; all reproduction inputs and test code were authored from trusted source evidence.

Primary investigation commands: git fetch origin main; git rev-parse origin/main; git worktree add --detach for each clean checkout; frozen install; Turbo build and the focused test command above; read-only GitHub issue fields, comments, timeline, label and PR metadata queries. Raw logs are retained locally; the complete public reproduction and relevant output are embedded above.