fix(coding-agent): kept claude home skills user-scoped

Skipped the home directory during Claude project skill walk-up so disabling Claude user skills cannot reload the same files as project skills.

Added regression coverage for the home-skill duplicate path with an enabled agents fallback.

Fixes #4648
This commit is contained in:
roboomp
2026-07-06 01:06:48 +00:00
parent dd3375981f
commit 914dc9551a
2 changed files with 27 additions and 8 deletions
+12 -8
View File
@@ -167,17 +167,21 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
const userSkillsDir = path.join(getUserClaude(ctx), "skills");
// Walk up from cwd finding .claude/skills/ in ancestors
// Walk up from cwd finding .claude/skills/ in ancestors. Skip $HOME:
// that path is already scanned as the Claude user source below, and scanning
// it again as project would bypass enableClaudeUser when project skills stay enabled.
const projectScans: Promise<LoadResult<Skill>>[] = [];
let current = ctx.cwd;
while (true) {
projectScans.push(
scanSkillsFromDir(ctx, {
dir: path.join(current, CONFIG_DIR, "skills"),
providerId: PROVIDER_ID,
level: "project",
}),
);
if (current !== ctx.home) {
projectScans.push(
scanSkillsFromDir(ctx, {
dir: path.join(current, CONFIG_DIR, "skills"),
providerId: PROVIDER_ID,
level: "project",
}),
);
}
if (current === (ctx.repoRoot ?? ctx.home)) break;
const parent = path.dirname(current);
if (parent === current) break; // filesystem root
@@ -113,6 +113,21 @@ describe("managed-skills discovery", () => {
expect(matches[0]?.source).toBe("agents:user");
});
it("does not resurrect disabled home claude user skills as project skills when cwd is under home", async () => {
// No repo root marker is created in tempHome/work. A Claude home skill must
// stay user-scoped only even while project skills remain enabled by default.
await writeSkill(path.join(tempHome, ".claude", "skills"), "home-only", "Disabled claude home skill.");
await writeSkill(path.join(tempHome, ".agents", "skills"), "home-only", "Enabled agents fallback.");
const { skills } = await loadSkills({
cwd: tempCwd,
enableClaudeUser: false,
});
const matches = skills.filter(s => s.name === "home-only");
expect(matches).toHaveLength(1);
expect(matches[0]?.source).toBe("agents:user");
expect(skills.some(s => s.name === "home-only" && s.source === "claude:project")).toBe(false);
});
it("preserves provider priority when duplicate authored providers are both enabled (#4648)", async () => {
await writeSkill(path.join(tempHome, ".claude", "skills"), "priority-authored", "Enabled claude.");
await writeSkill(path.join(tempHome, ".agents", "skills"), "priority-authored", "Enabled agents.");