#3175 · Plain-folder child environment selection fails
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
A child thread created with the project default cannot inherit a live parent that is already running directly in a non-git folder. The server replaces the implicit unmanaged request with a managed-worktree request without checking the parent environment, so downstream provisioning is asked to create a Git worktree from a non-git source. A second defect rejects repository-directory basenames containing Unicode letters before Git is reached. Two focused assertions fail on trusted origin/main in two clean checkouts with the same results.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| An implicit child environment under a live parent is converted to a managed worktree even when the parent runs unmanaged in a non-git folder. | Verified | The first regression assertion expected parent-environment reuse and received a host managed-worktree request. |
| A valid directory basename containing non-ASCII letters is rejected. | Verified | The second assertion called the path helper with a Unicode-letter basename and received invalid_request. |
| A root thread can use the same non-git project source directly. | Verified | The existing project-default suite passed all 7 tests, including its non-git case, and the source policy returns unmanaged when no base branch is available. |
| The issue's referenced prototype is safe or complete. | Unverified | No referenced branch, commit, patch, or binary was fetched or executed because issue data is untrusted. |
3. Environment
- Trusted repository:
get-bb/bb, commit6cdb4ba6125514b7660332cf311037bc09c82b0e. - macOS 26.6.1, arm64; Node v22.22.3; pnpm 10.34.4.
- No BB server, daemon, browser, network port, provider, or runtime data directory was used.
- The base checkout completed
pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run buildsuccessfully.
4. Minimal reproduction
- Clone trusted
get-bb/bb, detach at the base commit, and install with the frozen lockfile. - Save the regression test as
apps/server/test/threads/issue-3175-repro.test.ts. - Run:
pnpm exec turbo run test --filter=@bb/server -- test/threads/issue-3175-repro.test.ts
Expected: both tests pass: the child selects { type: "reuse", environmentId: "env-parent" }, and the Unicode basename is returned.
Actual:
Test Files 1 failed (1)
Tests 2 failed (2)
AssertionError: expected { type: 'host', …(2) } to deeply equal
{ type: 'reuse', …(1) }
Received workspace:
{ type: "managed-worktree", baseBranch: { kind: "default" } }
Error: Cannot derive repository directory name from source
at src/services/threads/worktree-paths.ts:24:11
Reproduction test
import type { Environment, Thread } from "@bb/domain";
import { describe, expect, it } from "vitest";
import { resolveCreateThreadEnvironment } from
"../../src/services/threads/thread-default-policy.js";
import { deriveRepoDirName } from
"../../src/services/threads/worktree-paths.js";
const parentThread: Pick<Thread,
"archivedAt" | "deletedAt" | "environmentId" | "id" |
"parentThreadId" | "projectId"> = {
archivedAt: null,
deletedAt: null,
environmentId: "env-parent",
id: "thr-parent",
parentThreadId: null,
projectId: "proj-one",
};
const parentEnvironment: Pick<Environment,
"id" | "isGitRepo" | "projectId" | "workspaceProvisionType"> = {
id: "env-parent",
isGitRepo: false,
projectId: "proj-one",
workspaceProvisionType: "unmanaged",
};
describe("plain-folder child thread regression", () => {
it("reuses its live parent's non-git unmanaged environment", () => {
const args = {
parentEnvironment,
parentThread,
projectId: "proj-one",
requestedEnvironment: {
type: "host" as const,
hostId: "host-one",
workspace: { type: "unmanaged" as const, path: null },
},
};
expect(resolveCreateThreadEnvironment(args)).toEqual({
type: "reuse",
environmentId: "env-parent",
});
});
it("accepts a Unicode repository directory name", () => {
expect(deriveRepoDirName("/tmp/репозиторий")).toBe("репозиторий");
});
});
5. Verification
The same test was copied into a second clean clone detached at 6cdb4ba6125514b7660332cf311037bc09c82b0e. After a separate frozen install, the identical Turbo command again reported one failed file and two failed tests with the same managed-worktree mismatch and Unicode-path error. No correction to the report was needed. No ports or data directories were used.
6. Root cause
The create route resolves the parent thread, but its call into environment-default policy passes only the thread record and requested environment. See thread-create.ts:702-709.
The policy then treats every live parent plus an implicit unmanaged host request as a managed-worktree request. It has no parent environment facts available, so it cannot distinguish a Git checkout from an ordinary folder. See thread-default-policy.ts:242-274. Downstream provisioning therefore receives a Git-specific request that cannot succeed for the source.
Separately, the directory-name helper accepts only ASCII letters and digits. Its basename validation rejects valid Unicode letters before constructing the managed target path. See worktree-paths.ts:1-30.
7. Proposed fix (first principles)
Load the exact environment referenced by the parent thread and provide a narrow typed view to the default policy. Reuse it only when the parent is live, both records and the request belong to the same project, the environment ID matches the parent's own ID, the environment is unmanaged, and it is not a Git repository. Preserve the managed-worktree default for Git parents and every mismatch. Update basename validation to accept Unicode letters and numbers while retaining the current whitespace, traversal, and leading-dash rejections. Cover both changes and negative guards with focused tests.
8. PR review
No linked open pull request was present in GitHub metadata at investigation time.
9. Related issues
Repository searches found other workspace and child-thread defects, but no exact duplicate of this combined environment-default and Unicode-path failure.
10. Appendix
Commands run:
git clone --branch main --single-branch git@github.com:get-bb/bb.git <checkout-a> git checkout --detach 6cdb4ba6125514b7660332cf311037bc09c82b0e pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm exec turbo run test --filter=@bb/server -- test/threads/issue-3175-repro.test.ts pnpm exec turbo run test --filter=@bb/server -- test/public/public-threads.project-default.test.ts git clone --branch main --single-branch git@github.com:get-bb/bb.git <checkout-b> git checkout --detach 6cdb4ba6125514b7660332cf311037bc09c82b0e pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run test --filter=@bb/server -- test/threads/issue-3175-repro.test.ts
The existing project-default suite passed 7/7. Both clean focused reproduction runs failed 2/2 in the same way. Issue content was handled only as untrusted claims; no issue-supplied branch, commit, patch, command, attachment, or external URL was used.