#3926 · Relocated Claude skills retain the default root
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
Moving Claude's configuration directory leaves skills from the default home directory in BB's discovered skill list. The Claude resolver correctly returns the configured directory, but the provider also declares the default directory as a static user root. BB intentionally scans both sets. A filesystem integration test reproduced the extra skill twice at the trusted main commit; no provider CLI, login, or live BB instance was involved.
2. Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Both default and configured user skills appear | Verified | Both clean runs returned configured-root-skill and default-root-skill. |
| The resolver ignores the relocation | Refuted as a root cause | The configured skill appears; removing only the static declaration fixes this test. |
| Static versus dynamic root precedence is unclear | Contract found | SDK explicitly says resolved roots are scanned beside declared roots and host-configured directories should be resolved. |
| The equivalent Codex setup is affected | Unverified | No Codex-specific reproduction was run. |
| The slash menu and Skills page expose the result | Not visually tested | Verified the filesystem discovery pipeline consumed by listing; did not run browser or CLI end to end. |
3. Environment
Public repository get-bb/bb; trusted origin/main commit above, also checked against GitHub main. macOS; Node v22.22.3; pnpm 9.15.0 via Corepack. Frozen install and full Turbo build completed (58 tasks). The first pnpm launcher was broken; an isolated wrapper invoking Corepack provided the repository-pinned version. Second checkout used a separate frozen install. Tests create and remove a fresh temporary home directory per run. No ports, database, provider process, real home skills, or runtime instance were used.
4. Minimal reproduction
- Check out the base commit and install dependencies with
pnpm install --frozen-lockfile --prefer-offline. - Save the regression test at
tests/integration/native-roots-golden/relocated-user-root.test.ts. - Run
pnpm exec turbo run test --filter=@bb/integration-tests -- relocated-user-root.test.ts. The test creates distinct default and configured skills, calls the existing provider-resolution and host-discovery integration pipeline, and asserts only the configured skill is listed.
Expected and actual assertion output (both base runs):
- Expected
+ Received
[
"configured-root-skill",
+ "default-root-skill",
]
The assertion fails on the unchanged production code. The same test also checks default-directory fallback after the relocation variable is cleared; that assertion is reached and passes in the local fix experiment.
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { expect, it, vi } from "vitest";
import { pipeline } from "./pipeline.js";
it("lists only the configured Claude user skills directory", async () => {
const homeDir = await mkdtemp(path.join(tmpdir(), "bb-relocated-skills-"));
try {
const configuredDir = path.join(homeDir, "configured");
for (const [directory, name] of [
[path.join(homeDir, ".claude"), "default-root-skill"],
[configuredDir, "configured-root-skill"],
] as const) {
const skillDir = path.join(directory, "skills", name);
await mkdir(skillDir, { recursive: true });
await writeFile(
path.join(skillDir, "SKILL.md"),
`---\nname: ${name}\ndescription: Test skill\n---\nTest content.\n`,
);
}
vi.stubEnv("CLAUDE_CONFIG_DIR", configuredDir);
const relocated = await pipeline({
providerId: "claude-code",
cwd: null,
homeDir,
});
expect(relocated.skills.map((skill) => skill.name).sort()).toEqual([
"configured-root-skill",
]);
vi.stubEnv("CLAUDE_CONFIG_DIR", "");
const defaults = await pipeline({
providerId: "claude-code",
cwd: null,
homeDir,
});
expect(defaults.skills.map((skill) => skill.name)).toEqual([
"default-root-skill",
]);
} finally {
vi.unstubAllEnvs();
await rm(homeDir, { recursive: true, force: true });
}
});
5. Root cause
plugins/provider-claude-code/src/native-roots.ts:17 declares .claude/skills as a home-relative user root. Its resolver at line 43 creates a second user root from the selected configuration directory. apps/server/src/services/providers/native-roots.ts:210 retains both declaration and resolver output. apps/host-daemon/src/command-handlers/list-commands.ts:361 appends declared and resolved scan roots; the two absolute paths are distinct, so deduplication cannot remove the stale one.
packages/plugin-sdk/src/backend-contract.ts:1556 says directories whose location is host-configured should be resolved rather than declared. packages/plugin-sdk/src/native-roots-contract.ts:1 explicitly documents additive resolution. The contained root cause is therefore the Claude declaration, not generic resolver precedence.
6. Proposed fix and automation limit
Remove the static Claude user skill declaration; keep project roots and resolve user skills through the existing resolver. The local three-line removal makes the focused regression pass, including default-directory fallback. All 364 Claude provider tests and its typecheck pass.
The full native-root suite reports 22 passing tests and one failing existing test: claude-code (config-dir) explicitly expects home-claude-skill and linked-home-skill. tests/integration/native-roots-golden/fixtures.ts:406 includes those names, and the generated goldens/claude-code.config-dir.json records them in both commands and skills. tests/integration/native-roots-golden/capture.ts:23 generates these golden files.
No PR was opened. Completing the fix requires correcting fixture expectations and the generated golden file, which violates this automation's prohibition on generated-file changes. The local fix was stopped and not pushed. A follow-up should update those expectations and regenerate the relevant golden, then rerun the suite. Resolver failure continues to be a separate fallback concern: without a static declaration, a failed resolver supplies no user roots, consistent with the existing command-root behavior.
7. Related issues
No open PR linking this issue was found in issue cross-reference metadata or open-PR search. Environment propagation is a separate stage from the additive scan reproduced here; no claims about other issues were experimentally verified.
8. Verification
The same agent repeated the unchanged regression in a second clean detached worktree at the exact base commit, using a separate frozen install and new temporary fixture directories. Command: pnpm exec turbo run test --filter=@bb/integration-tests -- relocated-user-root.test.ts. Result: the same expected failure and extra default-root-skill. This is a repeated verification, not an independent review. No report correction was needed.
9. Appendix
- First trusted-base failure
- Second clean-checkout failure
- Fix experiment: regression passes; existing golden test fails
Other validation: pnpm exec turbo run build; pnpm exec turbo run test typecheck --filter=bb-plugin-provider-claude-code; git diff --check. Logs have local checkout paths normalized. Issue data was treated only as untrusted claims; no issue-provided script, patch, branch, or external link was executed or fetched.