#3905 · Repository paths resolved from a nested workspace
2026-09-18 · trusted origin/main 8d35c2776bc7fd33f41a64e4c6dd7ebd2f021887
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
A workspace located below the repository root lists a modified tracked file, but its per-file patch is empty. The Git listing returns a repository-relative name that the patch reader subsequently interprets relative to the nested workspace. Context loading independently joins the same name to the workspace directory and reads a nonexistent doubled path. Direct calls to the production workspace and daemon functions reproduce both failures; the repository-root controls succeed. This is a backend reproduction, with no browser or desktop visual verification.
2. Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Nested workspace lists tracked changes but cannot load patches | Verified | Both runs list modules/client/sample.txt and return an empty patch only for the nested workspace. |
| Working-tree context fails while committed content works | Verified | Production readHostFile returns committed content, then throws ENOENT for the working-tree request built as the server route builds it. |
| Repository-root environment unaffected | Verified for fixture | Patch contains +after and disk read returns before/after lines. |
| Exact desktop text and release behavior | Unverified | No desktop UI or released binary was run. Evidence is current-main production backend code. |
| All special filenames, untracked paths, and diff modes | Unverified | The fixture exercises one ordinary tracked file in uncommitted mode. |
3. Environment
Darwin arm64; Node v22.22.3; Apple Git 2.50.1; repository-pinned pnpm 9.15.0; Vitest 4.1.1. The original origin URL redirects to get-bb/bb, verified through GitHub repository metadata. Both checkouts use the commit above.
No app instance, network listener, provider session, or user runtime data was used. Each test creates and removes its own temporary Git repository. Frozen installs and full Turbo builds succeeded in both checkouts (58/58 build tasks). The installed pnpm launcher initially failed; a temporary Corepack shim supplied the repository-pinned version. Build warnings about Electron import.meta were nonfatal.
4. Minimal reproduction
- Use a clean checkout of the trusted commit.
- Install and build, then save the regression test at the indicated location.
- Run the focused Turbo test. Two failures are the expected reproduction result.
git checkout --detach 8d35c2776bc7fd33f41a64e4c6dd7ebd2f021887 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build # Save the attached test to packages/host-workspace/test/issue-3905.test.ts pnpm exec turbo run test --filter=@bb/host-workspace -- --run test/issue-3905.test.ts
The test creates a new Git repository containing modules/client/sample.txt, commits its first line, appends a second line, and calls Workspace.diffFiles and Workspace.diffPatch from both repository and nested roots. A second test calls the actual daemon reader with the server route's path construction, including a successful committed-content control.
Expected: both roots return a patch containing +after; disk context returns before\nafter\n.
Actual, verbatim:
{"nested":true,"listed":["modules/client/sample.txt"],"patch":""}
AssertionError: expected '' to contain '+after'
ExpectedCommandDispatchError: Path does not exist: /tmp/bb-vt-lMOsci/issue-3905-isCt1T/modules/client/modules/client/sample.txt
Test Files 1 failed (1)
Tests 2 failed (2)
Complete reproduction test
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, expect, it } from "vitest";
import { Workspace } from "../src/workspace.js";
import { runGit } from "../src/git.js";
import { readHostFile } from "../../../apps/host-daemon/src/command-handlers/host-files.js";
const roots: string[] = [];
async function fixture() {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "issue-3905-"));
roots.push(root);
const nested = path.join(root, "modules", "client");
const relative = "modules/client/sample.txt";
await fs.mkdir(nested, { recursive: true });
await runGit(["init", "-b", "main"], { cwd: root });
await runGit(["config", "user.name", "Reproduction"], { cwd: root });
await runGit(["config", "user.email", "repro@example.com"], { cwd: root });
await fs.writeFile(path.join(root, relative), "before\n");
await runGit(["add", "--all"], { cwd: root });
await runGit(["commit", "-m", "Fixture baseline"], { cwd: root });
await fs.writeFile(path.join(root, relative), "before\nafter\n");
return { root, nested, relative };
}
afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));
});
it("loads the same listed patch from repository and nested workspace roots", async () => {
const { root, nested, relative } = await fixture();
const target = { type: "uncommitted" } as const;
for (const workspacePath of [root, nested]) {
const workspace = new Workspace(workspacePath);
const listing = await workspace.diffFiles({ target, maxFiles: 100 });
expect(listing.files.map((file) => file.path)).toContain(relative);
const patches = await workspace.diffPatch({ target, paths: [relative], maxBytesPerFile: 65536 });
console.log(JSON.stringify({ nested: workspacePath === nested, listed: listing.files.map((file) => file.path), patch: patches[0]?.patch }));
expect(patches[0]?.patch).toContain("+after");
}
});
it("loads disk context using the diff route's path construction", async () => {
const { root, nested, relative } = await fixture();
const control = await readHostFile({ type: "host.read_file", rootPath: root, path: path.join(root, relative) });
expect(control.content).toBe("before\nafter\n");
const historical = await readHostFile({ type: "host.read_file", rootPath: nested, path: path.join(nested, relative), ref: "HEAD" });
expect(historical.content).toBe("before\n");
const disk = await readHostFile({ type: "host.read_file", rootPath: nested, path: path.join(nested, relative) });
expect(disk.content).toBe("before\nafter\n");
});
5. Root cause
withDiffPathspec passes listed names through unchanged. runDiffCommands executes Git with the workspace path as cwd. The repository-relative name consequently selects nothing in a nested workspace. readTrackedPatchByPathCombined sees zero name-status entries and zero patch sections, so its unequal-length fallback does not run. diffPatch then emits an empty patch for the requested file.
The diffFile route forms the absolute disk path by joining environment.path and the repository-relative name. readHostFile forwards that exact path for disk reads. resolveReadablePath fails to resolve the duplicated path and also enforces containment within rootPath. In contrast, readFileFromGitRef derives a Git blob name relative to rootPath, which gives the correct repository-relative name for the committed-content request.
6. Proposed fix and automation decision
Establish an explicit repository-root basis for Git diff paths and disk context lookup, while preserving literal filename handling. Audit tracked, untracked, staged, rename, and special-character paths together. Context resolution must preserve the daemon's file-read containment checks and define which root is authorized; an ENOENT retry must not silently select another file or broaden that boundary.
No production fix or PR was attempted. The complete change spans workspace diff generation and server/daemon context resolution, exceeding this automation's one-subsystem condition. A patch-only change would leave the second reproduced failure unresolved. No open PR was found by the issue's cross-reference metadata or the open-PR search for issue 3905.
7. Verification
The same agent repeated the test in a second clean detached worktree at the exact trusted commit, with its own frozen dependency installation and successful full build. Only the attached reproduction test was added. The second run executed the same Turbo command and created new temporary fixture directories; no ports were needed. Both tests failed again for the same causes, in 1.04 seconds, with repository-root and committed-content controls passing. This was a repeat verification by the same agent. No report correction was required.
First run log · Second clean run log. Logs retain the failures and temporary fixture paths; personal checkout paths are replaced with generic checkout names.
8. Related issues
A small title search found diff-viewer feature requests and a provider-specific inline diff issue, but no equivalent defect was established. No related issue is asserted to be a duplicate.
9. Appendix and trust boundary
The issue's descriptions and suggested changes were treated as untrusted claims. No supplied script or patch was executed or copied. The reproduction was written from the trusted implementation and its existing tests. GitHub metadata supplied classification options and confirmed public repository visibility. No production source changes were made, so no passing-after-fix claim is made. The regression artifact intentionally fails on the recorded commit.
Investigation commands: git fetch origin main; git rev-parse origin/main; git checkout --detach at the recorded SHA; git worktree add --detach at the same SHA; frozen pnpm installation; full Turbo build; focused Turbo test; source inspection; GitHub read-only metadata and PR searches. Classification used the supplied SlopCop GitHub writer.
AGENT GENERATED