From 01f3e2d4680ff4baaef9d780bc900ef12d01e338 Mon Sep 17 00:00:00 2001 From: NewdlDewdl Date: Mon, 4 May 2026 19:04:31 -0500 Subject: [PATCH] fix(skills): require unique case-insensitive info matches --- CHANGELOG.md | 1 + src/cli/skills-cli.format.ts | 9 ++++++--- src/cli/skills-cli.test.ts | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1ad9a6b73d..7bc13c17e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3908,6 +3908,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- CLI/skills: require unique case-insensitive fallback matches in `openclaw skills info` so case-only collisions return not-found instead of showing guidance for the wrong skill. (#38713) - Agents/Ollama: forward the configured embedded-run timeout into the global undici stream timeout tuning so slow local Ollama runs no longer inherit the default stream cutoff instead of the operator-set run timeout. (#63175) Thanks @mindcraftreader and @vincentkoc. - Models/Codex: include `apiKey` in the codex provider catalog output so the Pi ModelRegistry validator no longer rejects the entry and silently drops all custom models from every provider in `models.json`. (#66180) Thanks @hoyyeva. - Tools/image+pdf: normalize configured provider/model refs before media-tool registry lookup so image and PDF tool runs stop rejecting valid Ollama vision models as unknown just because the tool path skipped the usual model-ref normalization step. (#59943) Thanks @yqli2420 and @vincentkoc. diff --git a/src/cli/skills-cli.format.ts b/src/cli/skills-cli.format.ts index 9f7a7dac9ed..179d5fc3ccf 100644 --- a/src/cli/skills-cli.format.ts +++ b/src/cli/skills-cli.format.ts @@ -123,11 +123,14 @@ function resolveSkillByName( } const lower = raw.toLowerCase(); - const caseInsensitive = report.skills.find( + const caseInsensitiveMatches = report.skills.filter( (s) => s.name.toLowerCase() === lower || s.skillKey.toLowerCase() === lower, ); - if (caseInsensitive) { - return caseInsensitive; + if (caseInsensitiveMatches.length === 1) { + return caseInsensitiveMatches[0] ?? null; + } + if (caseInsensitiveMatches.length > 1) { + return null; } const normalized = normalizeSkillLookupToken(raw); diff --git a/src/cli/skills-cli.test.ts b/src/cli/skills-cli.test.ts index 19af2860e09..a5486993d9a 100644 --- a/src/cli/skills-cli.test.ts +++ b/src/cli/skills-cli.test.ts @@ -206,6 +206,18 @@ describe("skills-cli", () => { expect(output).toContain("Spreadsheet helpers"); }); + it("returns not found for ambiguous case-insensitive matches", () => { + const report = createMockReport([ + createMockSkill({ name: "First Skill", skillKey: "Excel-XLSX", description: "first" }), + createMockSkill({ name: "Second Skill", skillKey: "excel-xlsx", description: "second" }), + ]); + + const output = formatSkillInfo(report, "EXCEL-XLSX", {}); + expect(output).toContain("not found"); + expect(output).not.toContain("first"); + expect(output).not.toContain("second"); + }); + it("returns not found for ambiguous normalized matches", () => { const report = createMockReport([ createMockSkill({ name: "Excel/XLSX", skillKey: "excel-slash", description: "first" }),