From 357317595c4079a6c69b3284064fe50a401f5234 Mon Sep 17 00:00:00 2001 From: Kigbnajd Date: Thu, 13 Aug 2026 21:54:02 +0200 Subject: [PATCH 1/2] fix(coding-agent): filter Claude custom tool modules --- packages/coding-agent/CHANGELOG.md | 4 ++ packages/coding-agent/src/discovery/claude.ts | 6 +- .../test/discovery/claude-tools.test.ts | 70 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 packages/coding-agent/test/discovery/claude-tools.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d0b7ec695..9cdb16b43 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Claude Code custom tool discovery attempting to import non-module files from `.claude/tools`. + ## [17.3.1] - 2026-08-13 ### Fixed diff --git a/packages/coding-agent/src/discovery/claude.ts b/packages/coding-agent/src/discovery/claude.ts index 6786c77b7..6f3b7c624 100644 --- a/packages/coding-agent/src/discovery/claude.ts +++ b/packages/coding-agent/src/discovery/claude.ts @@ -406,8 +406,9 @@ async function loadTools(ctx: LoadContext): Promise> { const userToolsDir = path.join(userBase, "tools"); const userResult = await loadFilesFromDir(ctx, userToolsDir, PROVIDER_ID, "user", { + extensions: ["ts", "js"], transform: (name, _content, path, source) => { - const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, ""); + const toolName = name.replace(/\.(ts|js)$/, ""); return { name: toolName, path, @@ -425,8 +426,9 @@ async function loadTools(ctx: LoadContext): Promise> { const projectToolsDir = path.join(projectBase, "tools"); const projectResult = await loadFilesFromDir(ctx, projectToolsDir, PROVIDER_ID, "project", { + extensions: ["ts", "js"], transform: (name, _content, path, source) => { - const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, ""); + const toolName = name.replace(/\.(ts|js)$/, ""); return { name: toolName, path, diff --git a/packages/coding-agent/test/discovery/claude-tools.test.ts b/packages/coding-agent/test/discovery/claude-tools.test.ts new file mode 100644 index 000000000..2124505bb --- /dev/null +++ b/packages/coding-agent/test/discovery/claude-tools.test.ts @@ -0,0 +1,70 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { type CustomTool, toolCapability } from "@oh-my-pi/pi-coding-agent/capability/tool"; +import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { initializeWithSettings, loadCapability } from "@oh-my-pi/pi-coding-agent/discovery"; +import { removeWithRetries } from "@oh-my-pi/pi-utils"; + +describe("Claude Code custom tool discovery", () => { + let root = ""; + let home = ""; + let project = ""; + let originalHome: string | undefined; + let originalClaudeConfigDir: string | undefined; + + beforeEach(async () => { + resetSettingsForTest(); + originalHome = process.env.HOME; + originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR; + delete process.env.CLAUDE_CONFIG_DIR; + root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-claude-tools-")); + home = path.join(root, "home"); + project = path.join(root, "project"); + process.env.HOME = home; + vi.spyOn(os, "homedir").mockReturnValue(home); + await fs.mkdir(path.join(project, ".git"), { recursive: true }); + const settings = await Settings.init({ inMemory: true, cwd: project }); + initializeWithSettings(settings); + }); + + afterEach(async () => { + resetSettingsForTest(); + vi.restoreAllMocks(); + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + if (originalClaudeConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR; + else process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir; + await removeWithRetries(root); + }); + + test("discovers only JavaScript and TypeScript modules", async () => { + const userTools = path.join(home, ".claude", "tools"); + const projectTools = path.join(project, ".claude", "tools"); + await fs.mkdir(userTools, { recursive: true }); + await fs.mkdir(projectTools, { recursive: true }); + await Promise.all([ + fs.writeFile(path.join(userTools, "user-tool.ts"), "export default () => ({});\n"), + fs.writeFile(path.join(projectTools, "project-tool.js"), "export default () => ({});\n"), + fs.writeFile(path.join(userTools, "helper.sh"), "#!/bin/sh\n"), + fs.writeFile(path.join(projectTools, "helper.bash"), "#!/bin/bash\n"), + fs.writeFile(path.join(projectTools, "helper.py"), "print('helper')\n"), + fs.writeFile(path.join(projectTools, "notes.md"), "# Notes\n"), + ]); + + const result = await loadCapability(toolCapability.id, { + cwd: project, + providers: ["claude"], + }); + const tools = result.items + .map(tool => ({ name: tool.name, level: tool.level })) + .sort((a, b) => a.name.localeCompare(b.name)); + + expect(result.warnings).toEqual([]); + expect(tools).toEqual([ + { name: "project-tool", level: "project" }, + { name: "user-tool", level: "user" }, + ]); + }); +}); From f9f7d878ea5b70155edfdbf69d3531bbcb7c2038 Mon Sep 17 00:00:00 2001 From: Kigbnajd Date: Thu, 13 Aug 2026 22:09:50 +0200 Subject: [PATCH 2/2] docs(changelog): attribute Claude tool fix --- packages/coding-agent/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 9cdb16b43..7d8f64f06 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed Claude Code custom tool discovery attempting to import non-module files from `.claude/tools`. +- Fixed Claude Code custom tool discovery attempting to import non-module files from `.claude/tools` ([#8471](https://github.com/can1357/oh-my-pi/pull/8471) by [@Kigbnajd](https://github.com/Kigbnajd)). ## [17.3.1] - 2026-08-13