From 06209553816decf381ff55e060ba1c1842d8e96d Mon Sep 17 00:00:00 2001 From: lakshya-dhariwal Date: Sun, 27 Sep 2026 19:13:27 +0530 Subject: [PATCH 1/2] fix(copilot): map task and web_search to canonical Task/WebSearch (#690) --- __tests__/hooks/copilot-canonicalize.test.ts | 10 ++++++++++ src/hooks/types.ts | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/__tests__/hooks/copilot-canonicalize.test.ts b/__tests__/hooks/copilot-canonicalize.test.ts index 16293d5af..98a88e488 100644 --- a/__tests__/hooks/copilot-canonicalize.test.ts +++ b/__tests__/hooks/copilot-canonicalize.test.ts @@ -66,3 +66,13 @@ describe("Copilot tool-input canonicalization (verified 1.0.71 captures)", () => expect(Object.keys(COPILOT_TOOL_INPUT_MAP).sort()).toEqual(["Edit", "Read", "Write"]); }); }); + +describe("Copilot tool-name canonicalization", () => { + it("maps `task` to Task so sub-agent policies fire on Copilot sessions", () => { + expect(canonicalizeToolName("task", "copilot")).toBe("Task"); + }); + + it("maps `web_search` to WebSearch", () => { + expect(canonicalizeToolName("web_search", "copilot")).toBe("WebSearch"); + }); +}); diff --git a/src/hooks/types.ts b/src/hooks/types.ts index e207f1b96..6f76bb5f1 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -275,6 +275,13 @@ export const COPILOT_TOOL_MAP: Record = { rg: "Grep", ls: "LS", web_fetch: "WebFetch", + // Copilot's sub-agent and web-search tools (documented). Without these a + // policy matching `Task` or `WebSearch` silently never fires on Copilot - + // the names pass through uncanonicalised, same failure mode the rest of + // this map exists to prevent. `ask_user` is deliberately unmapped: it has + // no filesystem or shell reach, so no builtin policy targets it. + task: "Task", + web_search: "WebSearch", }; /** From 1ef98d76d987a9bec8ae9456489e07726bb406bb Mon Sep 17 00:00:00 2001 From: lakshya-dhariwal Date: Sun, 27 Sep 2026 19:24:21 +0530 Subject: [PATCH 2/2] fix(hooks): canonicalize Copilot PascalCase 'Agent' tool name to Task PascalCase PreToolUse payloads report tool_name as the Claude tool name, and GitHub's hooks reference maps the runtime task tool to Agent there - so a Task policy still skipped Copilot's task calls under the shape this integration actually registers. --- __tests__/hooks/copilot-canonicalize.test.ts | 4 ++++ src/hooks/types.ts | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/__tests__/hooks/copilot-canonicalize.test.ts b/__tests__/hooks/copilot-canonicalize.test.ts index 98a88e488..77411ec56 100644 --- a/__tests__/hooks/copilot-canonicalize.test.ts +++ b/__tests__/hooks/copilot-canonicalize.test.ts @@ -72,6 +72,10 @@ describe("Copilot tool-name canonicalization", () => { expect(canonicalizeToolName("task", "copilot")).toBe("Task"); }); + it("maps `Agent` to Task - the PascalCase PreToolUse payload reports the Claude name", () => { + expect(canonicalizeToolName("Agent", "copilot")).toBe("Task"); + }); + it("maps `web_search` to WebSearch", () => { expect(canonicalizeToolName("web_search", "copilot")).toBe("WebSearch"); }); diff --git a/src/hooks/types.ts b/src/hooks/types.ts index 6f76bb5f1..a29a5b1c0 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -281,6 +281,13 @@ export const COPILOT_TOOL_MAP: Record = { // this map exists to prevent. `ask_user` is deliberately unmapped: it has // no filesystem or shell reach, so no builtin policy targets it. task: "Task", + // PascalCase `PreToolUse` payloads (the shape failproofai registers) report + // `tool_name` as the Claude tool name, and the docs table maps the runtime + // `task` tool to `Agent` there. Without this entry a Task policy still + // never fires on Copilot's PascalCase payload - the more common of the two + // shapes for this integration. + // Ref: https://docs.github.com/en/copilot/reference/hooks-reference + Agent: "Task", web_search: "WebSearch", };