diff --git a/layers/registry/server/utils/skill-behaviors.ts b/layers/registry/server/utils/skill-behaviors.ts index 2da3a14e..40d55dd0 100644 --- a/layers/registry/server/utils/skill-behaviors.ts +++ b/layers/registry/server/utils/skill-behaviors.ts @@ -12,12 +12,16 @@ export type SkillPageBehavior = Omit & { * The page holds SKILL.md and the file names the sync stored, never supporting * file content, so only those inputs reach the shared rules. `skilld run` * applies the same rules to every file. + * + * The panel is optional, so a rules failure returns no behaviors and reports + * itself. On 2026-10-05 the bundled rules failed their own parse, and the + * throw took every Skill page down with a 503. */ export function skillPageBehaviors(input: { raw: string | null assetPaths: readonly string[] source: { owner: string, repo: string, branch: string | null, skillPath: string | null } -}): SkillPageBehavior[] { +}, detect: typeof detectBehaviors = detectBehaviors): SkillPageBehavior[] { const files = [ ...(input.raw === null ? [] : [{ path: 'SKILL.md', text: input.raw }]), ...input.assetPaths.map(path => ({ path })), @@ -34,7 +38,19 @@ export function skillPageBehaviors(input: { }) return file && location.line !== null ? `${file}#L${location.line}` : file } - return detectBehaviors(files).map(behavior => ({ + let detected: Behavior[] + try { + detected = detect(files) + } + catch (error) { + emitOperationalEvent(createWideEvent({ + operation: 'skill-behaviors', + outcome: 'failed', + reason: error instanceof Error ? error.message : String(error), + })) + return [] + } + return detected.map(behavior => ({ ...behavior, locations: behavior.locations.map(location => ({ ...location, url: url(location) })), })) diff --git a/patches/skilld-protocol@2.5.0.patch b/patches/skilld-protocol@2.5.0.patch new file mode 100644 index 00000000..ff9e89f3 --- /dev/null +++ b/patches/skilld-protocol@2.5.0.patch @@ -0,0 +1,14 @@ +diff --git a/dist/behaviors.mjs b/dist/behaviors.mjs +index 901192e4a9fc582bfeb23b17aae8beb79861ef57..f5cabec0a991721d7ca8af58bfa83ec352dec05f 100644 +--- a/dist/behaviors.mjs ++++ b/dist/behaviors.mjs +@@ -418,7 +418,8 @@ var skill_behaviors_default = { + "commands": [["printenv"]], + "substrings": [ + "process.env", +- "import.meta.env", ++ // Split so a bundler that rewrites `import.meta` cannot reach the pattern. ++ "import." + "meta.env", + "deno.env", + "bun.env", + "os.environ", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc1ddff5..91d683a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -385,6 +385,7 @@ catalogs: patchedDependencies: hookable@5.5.3: 3b44da13a743bfa2eab02aef7246065e4dfd56e9878c206f34b51322832cfb2c hookable@6.1.1: 7530364d493fa95257cb863d105ed55a1bb4b46d5c9a178a6d64cb73028ed344 + skilld-protocol@2.5.0: 40578b24b5375959d96e2286d7fdcf6424e9827d7c72c7705df3352dce1c2aac importers: @@ -497,7 +498,7 @@ importers: version: 2.2.0 skilld-protocol: specifier: 'catalog:' - version: 2.5.0 + version: 2.5.0(patch_hash=40578b24b5375959d96e2286d7fdcf6424e9827d7c72c7705df3352dce1c2aac) skilld-sdk: specifier: 'catalog:' version: 0.1.2 @@ -18738,7 +18739,7 @@ snapshots: dependencies: fast-xml-parser: 5.10.1 - skilld-protocol@2.5.0: + skilld-protocol@2.5.0(patch_hash=40578b24b5375959d96e2286d7fdcf6424e9827d7c72c7705df3352dce1c2aac): dependencies: zod: 4.5.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 542604d7..6946d922 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -178,6 +178,7 @@ pipelines: patchedDependencies: hookable@5.5.3: patches/hookable@5.5.3.patch hookable@6.1.1: patches/hookable@6.1.1.patch + skilld-protocol@2.5.0: patches/skilld-protocol@2.5.0.patch catalog: '@antfu/eslint-config': ^9.3.0 '@cloudflare/sandbox': 1.0.0 diff --git a/test/unit/skill-page-behaviors.test.ts b/test/unit/skill-page-behaviors.test.ts index 73a82089..37de2b2d 100644 --- a/test/unit/skill-page-behaviors.test.ts +++ b/test/unit/skill-page-behaviors.test.ts @@ -34,4 +34,13 @@ describe('skillPageBehaviors', () => { expect(scripts?.locations[0]?.url).toBeNull() }) + + // 2026-10-05: the bundled rules failed their own parse, and every Skill page answered 503. + it('returns no behaviors, and keeps the page up, when the rules fail', () => { + const failing = () => { + throw new Error('patterns are lowercase') + } + + expect(skillPageBehaviors({ raw: '---\nname: setup\n---\n', assetPaths: ['run.py'], source }, failing)).toEqual([]) + }) })