From 6fa0dd8ccabfee961ba01ea92267eded7d07cc51 Mon Sep 17 00:00:00 2001 From: Scott Norteman Date: Sun, 11 Oct 2026 02:46:54 -0400 Subject: [PATCH] fix(provider-cursor): a skill tree deeper than the scan limit no longer hides every skill (#17389) Co-authored-by: Claude Opus 5.5 --- .../provider-cursor/src/server/skills.test.ts | 83 +++++++++++++++++++ packages/provider-cursor/src/server/skills.ts | 7 +- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/packages/provider-cursor/src/server/skills.test.ts b/packages/provider-cursor/src/server/skills.test.ts index 7831e5909e0f8725ef1dc57dd39e99fdbef4c133..7b6df4dcc0ae6e7f4291b05025e9b21068409fc6 100644 --- a/packages/provider-cursor/src/server/skills.test.ts +++ b/packages/provider-cursor/src/server/skills.test.ts @@ -102,6 +102,89 @@ describe("Cursor skills", () => { }), )); + it("skips a skill tree deeper than the limit instead of failing discovery", async () => + await runNode( + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDirectory = (prefix: string) => + fileSystem + .makeTempDirectoryScoped({ directory: NodeOS.tmpdir(), prefix }) + .pipe(Effect.flatMap((directory) => fileSystem.realPath(directory))); + const userHome = yield* tempDirectory("cursor-skills-home-"); + const workspace = yield* tempDirectory("cursor-skills-workspace-"); + const writeSkill = Effect.fn("writeCursorSkill")(function* (directory: string) { + yield* fileSystem.makeDirectory(directory, { recursive: true }); + yield* fileSystem.writeFileString(path.join(directory, "SKILL.md"), "---\n---\n"); + }); + const userRoot = path.join(userHome, ".claude", "skills"); + // The root is depth 0, so `deep-skill` sits at the depth limit and `below` past it. + const atLimit = path.join( + userRoot, + "deep", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "deep-skill", + ); + + yield* writeSkill(path.join(workspace, ".agents", "skills", "review")); + yield* writeSkill(atLimit); + yield* writeSkill(path.join(atLimit, "below")); + yield* writeSkill(path.join(userRoot, "later")); + + const expected = [ + { + name: "deep-skill", + path: path.join(atLimit, "SKILL.md"), + scope: "user", + enabled: true, + }, + { + name: "later", + path: path.join(userRoot, "later", "SKILL.md"), + scope: "user", + enabled: true, + }, + { + name: "review", + path: path.join(workspace, ".agents", "skills", "review", "SKILL.md"), + scope: "project", + enabled: true, + }, + ]; + expect(yield* discoverCursorSkills(workspace, { HOME: userHome })).toEqual(expected); + expect(yield* probeCursorSkills(workspace, { HOME: userHome })).toEqual(expected); + }), + )); + + it("still fails discovery when a root holds more entries than the scan allows", async () => + await runNode( + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const userHome = yield* fileSystem.makeTempDirectoryScoped({ + directory: NodeOS.tmpdir(), + prefix: "cursor-skills-home-", + }); + const root = path.join(userHome, ".cursor", "skills"); + yield* fileSystem.makeDirectory(root, { recursive: true }); + yield* Effect.forEach( + Array.from({ length: 10_001 }, (_, index) => path.join(root, `entry-${index}`)), + (file) => fileSystem.writeFileString(file, ""), + { concurrency: 64, discard: true }, + ); + + const error = yield* Effect.flip(probeCursorSkills(undefined, { HOME: userHome })); + expect(error.message).toContain("scan-budget-exhausted"); + }), + )); + it.skipIf(!symlinksSupported)( "treats a symlinked skill outside the root as a package boundary", async () => diff --git a/packages/provider-cursor/src/server/skills.ts b/packages/provider-cursor/src/server/skills.ts index 371aa75352d65023c4003e7877ff322cfef081bc..9f119f28088d37c542049893709c58fd02530021 100644 --- a/packages/provider-cursor/src/server/skills.ts +++ b/packages/provider-cursor/src/server/skills.ts @@ -204,10 +204,9 @@ const discoverSkillsInRoot = Effect.fn("discoverCursorSkillsInRoot")(function* ( const child = path.join(directory, entry); const info = yield* orUndefined(fileSystem.stat(child), input.budget); if (info?.type !== "Directory") continue; - if (depth >= MAX_SKILL_DEPTH) { - input.budget.exhausted = true; - return; - } + // A tree deeper than the limit is skipped rather than fatal: the same tree would fail + // every refresh and hide every other skill with it. + if (depth >= MAX_SKILL_DEPTH) continue; yield* visit(child, depth + 1); } }); -- 2.54.0