diff --git a/src/modes/agent/parse-tools.ts b/src/modes/agent/parse-tools.ts index 9b2fdcb90..639c9131a 100644 --- a/src/modes/agent/parse-tools.ts +++ b/src/modes/agent/parse-tools.ts @@ -1,22 +1,33 @@ export function parseAllowedTools(claudeArgs: string): string[] { // Match --allowedTools or --allowed-tools followed by the value // Handle both quoted and unquoted values + // Use /g flag to find ALL occurrences, not just the first one const patterns = [ - /--(?:allowedTools|allowed-tools)\s+"([^"]+)"/, // Double quoted - /--(?:allowedTools|allowed-tools)\s+'([^']+)'/, // Single quoted - /--(?:allowedTools|allowed-tools)\s+([^\s]+)/, // Unquoted + /--(?:allowedTools|allowed-tools)\s+"([^"]+)"/g, // Double quoted + /--(?:allowedTools|allowed-tools)\s+'([^']+)'/g, // Single quoted + /--(?:allowedTools|allowed-tools)\s+([^'"\s][^\s]*)/g, // Unquoted (must not start with quote) ]; + const tools: string[] = []; + const seen = new Set(); + for (const pattern of patterns) { - const match = claudeArgs.match(pattern); - if (match && match[1]) { - // Don't return if the value starts with -- (another flag) - if (match[1].startsWith("--")) { - return []; + for (const match of claudeArgs.matchAll(pattern)) { + if (match[1]) { + // Don't add if the value starts with -- (another flag) + if (match[1].startsWith("--")) { + continue; + } + for (const tool of match[1].split(",")) { + const trimmed = tool.trim(); + if (trimmed && !seen.has(trimmed)) { + seen.add(trimmed); + tools.push(trimmed); + } + } } - return match[1].split(",").map((t) => t.trim()); } } - return []; + return tools; } diff --git a/test/modes/parse-tools.test.ts b/test/modes/parse-tools.test.ts index e88e8001c..84916fb13 100644 --- a/test/modes/parse-tools.test.ts +++ b/test/modes/parse-tools.test.ts @@ -35,12 +35,44 @@ describe("parseAllowedTools", () => { expect(parseAllowedTools("")).toEqual([]); }); - test("handles duplicate --allowedTools flags", () => { + test("handles --allowedTools followed by another --allowedTools flag", () => { const args = "--allowedTools --allowedTools mcp__github__*"; - // Should not match the first one since the value is another flag + // The second --allowedTools is consumed as a value of the first, then skipped. + // This is an edge case with malformed input - returns empty. expect(parseAllowedTools(args)).toEqual([]); }); + test("parses multiple separate --allowed-tools flags", () => { + const args = + "--allowed-tools 'mcp__context7__*' --allowed-tools 'Read,Glob' --allowed-tools 'mcp__github_inline_comment__*'"; + expect(parseAllowedTools(args)).toEqual([ + "mcp__context7__*", + "Read", + "Glob", + "mcp__github_inline_comment__*", + ]); + }); + + test("parses multiple --allowed-tools flags on separate lines", () => { + const args = `--model 'claude-haiku' +--allowed-tools 'mcp__context7__*' +--allowed-tools 'Read,Glob,Grep' +--allowed-tools 'mcp__github_inline_comment__create_inline_comment'`; + expect(parseAllowedTools(args)).toEqual([ + "mcp__context7__*", + "Read", + "Glob", + "Grep", + "mcp__github_inline_comment__create_inline_comment", + ]); + }); + + test("deduplicates tools from multiple flags", () => { + const args = + "--allowed-tools 'Read,Glob' --allowed-tools 'Glob,Grep' --allowed-tools 'Read'"; + expect(parseAllowedTools(args)).toEqual(["Read", "Glob", "Grep"]); + }); + test("handles typo --alloedTools", () => { const args = "--alloedTools mcp__github__*"; expect(parseAllowedTools(args)).toEqual([]);