From 4b8df5f04c7ee4719e260418d0040b1c2dbf510c Mon Sep 17 00:00:00 2001 From: failproofai-hook-sync Date: Thu, 9 Jul 2026 10:21:59 +0000 Subject: [PATCH 1/5] feat: sync agent CLI harnesses with upstream docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Append newly-documented hook events and fix two timeout-unit bugs found by comparing each integrated CLI's upstream docs to this repo. Scope 1 (event names): - Claude: append `Setup`, `MessageDisplay` (no event map — build stays green) - Codex: append `subagent_start`, `pre_compact`, `post_compact`, `subagent_stop`. CODEX_EVENT_MAP entries are intentionally NOT added so `tsc` fails until a reviewer picks the canonical HookEventType (per the map-bearing-CLI rule) — see PR checklist. - Copilot: append `PostToolUseFailure`, `ErrorOccurred`, `PreCompact`, `PermissionRequest` (docs show explicit PascalCase variants; no event map — build stays green). `notification` / `subagentStart` are camelCase-only upstream and deferred to the reviewer. Scope 3 (settings-file shape, #482-class timeout-unit fix): - Claude and Cursor read `timeout` in SECONDS per their docs (Claude "60 for agent"; Cursor "Execution timeout in seconds"), so the old `60000` meant ~16.7h. Corrected writer + dogfood fixtures + unit tests to `60`. Gemini's `timeout` is genuinely milliseconds (default 60000) and is left unchanged. Verified each claim by fetching the primary docs directly. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.json | 52 ++++++++++++++-------------- .cursor/hooks.json | 12 +++---- CHANGELOG.md | 8 +++++ __tests__/hooks/integrations.test.ts | 6 ++-- __tests__/hooks/manager.test.ts | 8 ++--- src/hooks/integrations.ts | 14 +++++--- src/hooks/types.ts | 26 ++++++++++++++ 7 files changed, 83 insertions(+), 43 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 1f3b5886..fe134c20 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -6,7 +6,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook SessionStart", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -18,7 +18,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook SessionEnd", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -30,7 +30,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook UserPromptSubmit", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -42,7 +42,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PreToolUse", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -54,7 +54,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PermissionRequest", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -66,7 +66,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PermissionDenied", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -78,7 +78,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PostToolUse", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -90,7 +90,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PostToolUseFailure", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -102,7 +102,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook Notification", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -114,7 +114,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook SubagentStart", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -126,7 +126,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook SubagentStop", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -138,7 +138,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook TaskCreated", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -150,7 +150,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook TaskCompleted", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -162,7 +162,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook Stop", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -174,7 +174,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook StopFailure", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -186,7 +186,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook TeammateIdle", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -198,7 +198,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook InstructionsLoaded", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -210,7 +210,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook ConfigChange", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -222,7 +222,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook CwdChanged", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -234,7 +234,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook FileChanged", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -246,7 +246,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook WorktreeCreate", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -258,7 +258,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook WorktreeRemove", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -270,7 +270,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PreCompact", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -282,7 +282,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook PostCompact", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -294,7 +294,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook Elicitation", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] @@ -306,7 +306,7 @@ { "type": "command", "command": "bun $CLAUDE_PROJECT_DIR/bin/failproofai.mjs --hook ElicitationResult", - "timeout": 60000, + "timeout": 60, "__failproofai_hook__": true } ] diff --git a/.cursor/hooks.json b/.cursor/hooks.json index 6cc9637b..395bb640 100644 --- a/.cursor/hooks.json +++ b/.cursor/hooks.json @@ -5,42 +5,42 @@ { "type": "command", "command": "bun bin/failproofai.mjs --hook sessionStart --cli cursor", - "timeout": 60000 + "timeout": 60 } ], "sessionEnd": [ { "type": "command", "command": "bun bin/failproofai.mjs --hook sessionEnd --cli cursor", - "timeout": 60000 + "timeout": 60 } ], "beforeSubmitPrompt": [ { "type": "command", "command": "bun bin/failproofai.mjs --hook beforeSubmitPrompt --cli cursor", - "timeout": 60000 + "timeout": 60 } ], "preToolUse": [ { "type": "command", "command": "bun bin/failproofai.mjs --hook preToolUse --cli cursor", - "timeout": 60000 + "timeout": 60 } ], "postToolUse": [ { "type": "command", "command": "bun bin/failproofai.mjs --hook postToolUse --cli cursor", - "timeout": 60000 + "timeout": 60 } ], "stop": [ { "type": "command", "command": "bun bin/failproofai.mjs --hook stop --cli cursor", - "timeout": 60000 + "timeout": 60 } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 85b45213..e28a5fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.0.13-beta.0 — 2026-07-09 + +### Features +- Sync agent CLI hook harnesses with upstream docs. Append newly-documented hook events: Claude `Setup` + `MessageDisplay`; Codex `subagent_start`/`pre_compact`/`post_compact`/`subagent_stop` (their `CODEX_EVENT_MAP` canonical mappings are intentionally left for a reviewer, so `tsc` gates the change); Copilot `PostToolUseFailure`/`ErrorOccurred`/`PreCompact`/`PermissionRequest`. (#485) + +### Fixes +- Correct the Claude and Cursor hook `timeout` unit from `60000` to `60`. Both CLIs read `timeout` in seconds per their upstream docs (Claude: "Seconds before canceling … 60 for agent"; Cursor: "Execution timeout in seconds"), so the old `60000` meant ~16.7h instead of 60s. Gemini's `timeout` is genuinely milliseconds (default 60000) and is left unchanged. (#485) + ## 0.0.12-beta.0 — 2026-07-09 ### Features diff --git a/__tests__/hooks/integrations.test.ts b/__tests__/hooks/integrations.test.ts index b955ace8..b447154b 100644 --- a/__tests__/hooks/integrations.test.ts +++ b/__tests__/hooks/integrations.test.ts @@ -125,7 +125,7 @@ describe("Claude Code integration", () => { const entry = claudeCode.buildHookEntry("/usr/bin/failproofai", "PreToolUse", "user"); expect(entry.command).toBe('"/usr/bin/failproofai" --hook PreToolUse'); expect(entry.command).not.toContain("--cli"); - expect(entry.timeout).toBe(60_000); + expect(entry.timeout).toBe(60); expect(entry[FAILPROOFAI_HOOK_MARKER]).toBe(true); }); @@ -191,7 +191,7 @@ describe("OpenAI Codex integration", () => { expect(codex.scopes).toEqual(["user", "project"]); }); - it("eventTypes are exactly the 6 documented Codex events (snake_case)", () => { + it("eventTypes are exactly the 10 documented Codex events (snake_case)", () => { expect(codex.eventTypes).toEqual(CODEX_HOOK_EVENT_TYPES); // PR 185 omitted permission_request — make sure we have it. expect(codex.eventTypes).toContain("permission_request"); @@ -414,7 +414,7 @@ describe("Cursor Agent integration", () => { const entry = cursor.buildHookEntry("/usr/bin/failproofai", "preToolUse", "user") as Record; expect(entry.type).toBe("command"); expect(entry.command).toBe('"/usr/bin/failproofai" --hook preToolUse --cli cursor'); - expect(entry.timeout).toBe(60_000); + expect(entry.timeout).toBe(60); expect(entry[FAILPROOFAI_HOOK_MARKER]).toBe(true); // Cursor entries use the Claude-style `command` field, not Copilot's bash/powershell split. expect(entry.bash).toBeUndefined(); diff --git a/__tests__/hooks/manager.test.ts b/__tests__/hooks/manager.test.ts index 0956726d..c218ef66 100644 --- a/__tests__/hooks/manager.test.ts +++ b/__tests__/hooks/manager.test.ts @@ -65,7 +65,7 @@ describe("hooks/manager", () => { }); describe("installHooks", () => { - it("installs hooks for all 28 event types into empty settings", async () => { + it("installs hooks for all 30 event types into empty settings", async () => { vi.mocked(existsSync).mockReturnValue(true); vi.mocked(readFileSync).mockReturnValue("{}"); @@ -77,14 +77,14 @@ describe("hooks/manager", () => { expect(path).toBe(USER_SETTINGS_PATH); const written = JSON.parse(content as string); - expect(Object.keys(written.hooks)).toHaveLength(28); + expect(Object.keys(written.hooks)).toHaveLength(30); for (const [eventType, matchers] of Object.entries(written.hooks)) { expect(matchers).toHaveLength(1); const hook = (matchers as Array<{ hooks: Array> }>)[0].hooks[0]; expect(hook.__failproofai_hook__).toBe(true); expect(hook.type).toBe("command"); - expect(hook.timeout).toBe(60_000); + expect(hook.timeout).toBe(60); expect(hook.command).toBe(`"/usr/local/bin/failproofai" --hook ${eventType}`); } }); @@ -231,7 +231,7 @@ describe("hooks/manager", () => { expect(writeFileSync).toHaveBeenCalledOnce(); const [, content] = vi.mocked(writeFileSync).mock.calls[0]; const written = JSON.parse(content as string); - expect(Object.keys(written.hooks)).toHaveLength(28); + expect(Object.keys(written.hooks)).toHaveLength(30); }); it("uses 'where' on Windows and handles multi-line output", async () => { diff --git a/src/hooks/integrations.ts b/src/hooks/integrations.ts index 99638229..60476cd1 100644 --- a/src/hooks/integrations.ts +++ b/src/hooks/integrations.ts @@ -154,7 +154,10 @@ export const claudeCode: Integration = { return { type: "command", command, - timeout: 60_000, + // Claude reads `timeout` in SECONDS per https://code.claude.com/docs/en/hooks + // ("Seconds before canceling. Defaults: 600 for command ...; 60 for agent"), + // NOT milliseconds. 60 = 60s; the old 60000 meant ~16.7h. (#482-class unit fix) + timeout: 60, [FAILPROOFAI_HOOK_MARKER]: true, }; }, @@ -286,7 +289,8 @@ export const codex: Integration = { return { type: "command", // Codex reads `timeout` in SECONDS (its `timeout_sec` field, default 600) — - // NOT milliseconds like Claude/Cursor/Gemini. 60 = 60s, not 60000ms (~16.7h). + // like Claude/Cursor/Copilot, and unlike Gemini, whose `timeout` is in + // milliseconds (default 60000). 60 = 60s, not 60000ms (~16.7h). command, timeout: 60, [FAILPROOFAI_HOOK_MARKER]: true, @@ -591,11 +595,13 @@ export const cursor: Integration = { scope === "project" ? `npx -y failproofai --hook ${eventType} --cli cursor` : `"${binaryPath}" --hook ${eventType} --cli cursor`; - // `timeout` is documented as ms in Cursor's schema (matches Claude). + // `timeout` is documented in SECONDS in Cursor's schema per + // https://cursor.com/docs/hooks ("Execution timeout in seconds"; doc examples + // use 30 and 10), NOT milliseconds. 60 = 60s; the old 60000 meant ~16.7h. return { type: "command", command, - timeout: 60_000, + timeout: 60, [FAILPROOFAI_HOOK_MARKER]: true, }; }, diff --git a/src/hooks/types.ts b/src/hooks/types.ts index f9483b7e..fe5369fd 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -18,6 +18,16 @@ export const CODEX_HOOK_EVENT_TYPES = [ "post_tool_use", "user_prompt_submit", "stop", + // Newly documented upstream (https://developers.openai.com/codex/hooks) — + // snake_case forms of the documented SubagentStart / PreCompact / PostCompact / + // SubagentStop events. Their CODEX_EVENT_MAP entries are intentionally NOT added + // here: the canonical HookEventType is a reviewer decision, and the missing keys + // make the exhaustive `Record` below fail tsc + // on purpose so this drift is not silently half-applied. See PR body checklist. + "subagent_start", + "pre_compact", + "post_compact", + "subagent_stop", ] as const; export type CodexHookEventType = (typeof CODEX_HOOK_EVENT_TYPES)[number]; @@ -92,6 +102,16 @@ export const COPILOT_HOOK_EVENT_TYPES = [ "PostToolUse", "Stop", "SubagentStop", + // Newly documented upstream (cli-hooks-reference) with an explicit PascalCase + // "VS Code compatible" variant shown in the docs. No COPILOT_EVENT_MAP exists + // (Copilot names are already Pascal), so appending keeps the build green. + // NOTE: `notification` and `subagentStart` are also newly documented but appear + // camelCase-ONLY (no PascalCase variant shown), so they are deferred to the PR + // reviewer checklist rather than guessing a casing. + "PostToolUseFailure", + "ErrorOccurred", + "PreCompact", + "PermissionRequest", ] as const; export type CopilotHookEventType = (typeof COPILOT_HOOK_EVENT_TYPES)[number]; @@ -556,6 +576,12 @@ export const HOOK_EVENT_TYPES = [ "ElicitationResult", "UserPromptExpansion", "PostToolBatch", + // Newly documented upstream (https://code.claude.com/docs/en/hooks lifecycle + // table): `Setup` (fires on --init/--maintenance) and `MessageDisplay` (fires + // while assistant message text is displayed; docs lower its timeout default to + // 10s). Claude is canonical (no event map), so appending keeps the build green. + "Setup", + "MessageDisplay", ] as const; export type HookEventType = (typeof HOOK_EVENT_TYPES)[number]; From 2cd5eda27a88e39bd91106accdb31b8afe0b29c9 Mon Sep 17 00:00:00 2001 From: chhhee10 Date: Thu, 9 Jul 2026 16:37:30 +0530 Subject: [PATCH 2/5] fix(codex): complete CODEX_EVENT_MAP for the 4 newly-synced events The prior commit added subagent_start/pre_compact/post_compact/subagent_stop to CODEX_HOOK_EVENT_TYPES but intentionally left their CODEX_EVENT_MAP entries out to fail tsc as a reviewer flag. Each has an exact 1:1 canonical HookEventType (SubagentStart/PreCompact/PostCompact/SubagentStop, all already in HOOK_EVENT_TYPES), so fill them in directly. Besides unblocking tsc, this fixes a real runtime bug: with no mapping, writeHookEntries resolved CODEX_EVENT_MAP[event] to undefined and collapsed all four events into a single literal "undefined" key in users' .codex/hooks.json (the source of the removeHooksFromFile "7 vs 10" test failure). The map is exhaustive, so tsc still guards against a future event-added-mapping-missing drift. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/hooks/types.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/hooks/types.ts b/src/hooks/types.ts index fe5369fd..d6a491c1 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -20,10 +20,12 @@ export const CODEX_HOOK_EVENT_TYPES = [ "stop", // Newly documented upstream (https://developers.openai.com/codex/hooks) — // snake_case forms of the documented SubagentStart / PreCompact / PostCompact / - // SubagentStop events. Their CODEX_EVENT_MAP entries are intentionally NOT added - // here: the canonical HookEventType is a reviewer decision, and the missing keys - // make the exhaustive `Record` below fail tsc - // on purpose so this drift is not silently half-applied. See PR body checklist. + // SubagentStop events. Each has an exact 1:1 canonical HookEventType (already + // present in HOOK_EVENT_TYPES), so their CODEX_EVENT_MAP entries below are + // filled in directly. The map is an exhaustive `Record`, so tsc guarantees every event here keeps a mapping — a + // partial sync (event added, mapping missing) fails the build instead of + // silently writing an `undefined` event key into users' .codex/hooks.json. "subagent_start", "pre_compact", "post_compact", @@ -38,6 +40,10 @@ export const CODEX_EVENT_MAP: Record = { post_tool_use: "PostToolUse", user_prompt_submit: "UserPromptSubmit", stop: "Stop", + subagent_start: "SubagentStart", + pre_compact: "PreCompact", + post_compact: "PostCompact", + subagent_stop: "SubagentStop", }; /** From 5830f7c431cbc99097f1a79b2dad9c8be5517c18 Mon Sep 17 00:00:00 2001 From: chhhee10 Date: Thu, 9 Jul 2026 16:44:00 +0530 Subject: [PATCH 3/5] fix(claude): keep Setup, defer observe-only MessageDisplay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prior commit appended both Setup and MessageDisplay to HOOK_EVENT_TYPES. writeHookEntries installs a failproofai hook for every entry in that array, so appending MessageDisplay would spawn a subprocess on every assistant message render — and the docs mark MessageDisplay observe-only (cannot block or modify, no matcher support), so that cost buys zero enforcement value. Keep Setup (fires only on --init/--maintenance; low-frequency, harmless to install) and defer MessageDisplay to the reviewer checklist with a documented NOTE, mirroring how the sync bot defers uncertain items. Net event count is 29, so update the two manager.test count assertions and the description. Co-Authored-By: Claude Opus 4.8 (1M context) --- __tests__/hooks/manager.test.ts | 6 +++--- src/hooks/types.ts | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/__tests__/hooks/manager.test.ts b/__tests__/hooks/manager.test.ts index c218ef66..c10f5aa4 100644 --- a/__tests__/hooks/manager.test.ts +++ b/__tests__/hooks/manager.test.ts @@ -65,7 +65,7 @@ describe("hooks/manager", () => { }); describe("installHooks", () => { - it("installs hooks for all 30 event types into empty settings", async () => { + it("installs hooks for all 29 event types into empty settings", async () => { vi.mocked(existsSync).mockReturnValue(true); vi.mocked(readFileSync).mockReturnValue("{}"); @@ -77,7 +77,7 @@ describe("hooks/manager", () => { expect(path).toBe(USER_SETTINGS_PATH); const written = JSON.parse(content as string); - expect(Object.keys(written.hooks)).toHaveLength(30); + expect(Object.keys(written.hooks)).toHaveLength(29); for (const [eventType, matchers] of Object.entries(written.hooks)) { expect(matchers).toHaveLength(1); @@ -231,7 +231,7 @@ describe("hooks/manager", () => { expect(writeFileSync).toHaveBeenCalledOnce(); const [, content] = vi.mocked(writeFileSync).mock.calls[0]; const written = JSON.parse(content as string); - expect(Object.keys(written.hooks)).toHaveLength(30); + expect(Object.keys(written.hooks)).toHaveLength(29); }); it("uses 'where' on Windows and handles multi-line output", async () => { diff --git a/src/hooks/types.ts b/src/hooks/types.ts index d6a491c1..fe85e535 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -583,11 +583,17 @@ export const HOOK_EVENT_TYPES = [ "UserPromptExpansion", "PostToolBatch", // Newly documented upstream (https://code.claude.com/docs/en/hooks lifecycle - // table): `Setup` (fires on --init/--maintenance) and `MessageDisplay` (fires - // while assistant message text is displayed; docs lower its timeout default to - // 10s). Claude is canonical (no event map), so appending keeps the build green. + // table). `Setup` fires only on `--init`/`--maintenance` (low-frequency), so it + // is appended and installed like any other event. + // + // `MessageDisplay` is intentionally NOT appended. The docs mark it observe-only + // (it cannot block or modify anything) and note it fires on *every* assistant + // message display with no matcher support. Since `writeHookEntries` installs a + // hook for every entry in this array, appending it would spawn a failproofai + // subprocess on every message render for zero enforcement value. Deferred to the + // PR reviewer checklist; add it here only if a future observe-only use case + // (e.g. redaction/telemetry) justifies the per-message cost. "Setup", - "MessageDisplay", ] as const; export type HookEventType = (typeof HOOK_EVENT_TYPES)[number]; From 22727681d1080222fc881c24a10a50492d5e951e Mon Sep 17 00:00:00 2001 From: chhhee10 Date: Thu, 9 Jul 2026 16:45:52 +0530 Subject: [PATCH 4/5] feat(copilot): append Notification, correct the deferral note The prior commit's comment claimed both notification and subagentStart are camelCase-only upstream. Re-checking the cli-hooks-reference: Notification DOES have a documented PascalCase "VS Code compatible" variant, so append it to COPILOT_HOOK_EVENT_TYPES (no event map, build stays green). Only subagentStart is genuinely camelCase-only, so it stays deferred to the reviewer checklist. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/hooks/types.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/hooks/types.ts b/src/hooks/types.ts index fe85e535..7eea07c7 100644 --- a/src/hooks/types.ts +++ b/src/hooks/types.ts @@ -108,16 +108,18 @@ export const COPILOT_HOOK_EVENT_TYPES = [ "PostToolUse", "Stop", "SubagentStop", - // Newly documented upstream (cli-hooks-reference) with an explicit PascalCase - // "VS Code compatible" variant shown in the docs. No COPILOT_EVENT_MAP exists - // (Copilot names are already Pascal), so appending keeps the build green. - // NOTE: `notification` and `subagentStart` are also newly documented but appear - // camelCase-ONLY (no PascalCase variant shown), so they are deferred to the PR - // reviewer checklist rather than guessing a casing. + // Newly documented upstream (cli-hooks-reference), each with an explicit + // PascalCase "VS Code compatible" variant shown in the docs. No COPILOT_EVENT_MAP + // exists (Copilot names are already Pascal), so appending keeps the build green. + // NOTE: `subagentStart` is also newly documented but appears camelCase-ONLY (no + // PascalCase variant listed), so it is deferred to the reviewer checklist rather + // than guessing a casing. `notification`, by contrast, DOES have a documented + // `Notification` PascalCase variant, so it is appended below. "PostToolUseFailure", "ErrorOccurred", "PreCompact", "PermissionRequest", + "Notification", ] as const; export type CopilotHookEventType = (typeof COPILOT_HOOK_EVENT_TYPES)[number]; From 2eaadd0d2b7451c07207b8a695d6454fd3cf5fed Mon Sep 17 00:00:00 2001 From: chhhee10 Date: Thu, 9 Jul 2026 16:46:55 +0530 Subject: [PATCH 5/5] docs: correct Codex timeout field name + update changelog for review - The Codex timeout comment said the field is `timeout_sec`; docs confirm it is literally `timeout` (unit seconds, default 600). Fix the comment. - Rewrite the 0.0.13-beta.0 changelog entry to describe the reviewed result: Codex map completed, Copilot Notification added, MessageDisplay deferred. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 2 +- src/hooks/integrations.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e28a5fda..57807d4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 0.0.13-beta.0 — 2026-07-09 ### Features -- Sync agent CLI hook harnesses with upstream docs. Append newly-documented hook events: Claude `Setup` + `MessageDisplay`; Codex `subagent_start`/`pre_compact`/`post_compact`/`subagent_stop` (their `CODEX_EVENT_MAP` canonical mappings are intentionally left for a reviewer, so `tsc` gates the change); Copilot `PostToolUseFailure`/`ErrorOccurred`/`PreCompact`/`PermissionRequest`. (#485) +- Sync agent CLI hook harnesses with upstream docs. Append newly-documented hook events: Codex `subagent_start`/`pre_compact`/`post_compact`/`subagent_stop` (with their `CODEX_EVENT_MAP` canonical mappings to `SubagentStart`/`PreCompact`/`PostCompact`/`SubagentStop`); Copilot `PostToolUseFailure`/`ErrorOccurred`/`PreCompact`/`PermissionRequest`/`Notification`; Claude `Setup`. Claude's `MessageDisplay` is documented but deliberately not installed — it is observe-only and fires on every assistant message, so a per-message hook would add cost with no enforcement value. (#485) ### Fixes - Correct the Claude and Cursor hook `timeout` unit from `60000` to `60`. Both CLIs read `timeout` in seconds per their upstream docs (Claude: "Seconds before canceling … 60 for agent"; Cursor: "Execution timeout in seconds"), so the old `60000` meant ~16.7h instead of 60s. Gemini's `timeout` is genuinely milliseconds (default 60000) and is left unchanged. (#485) diff --git a/src/hooks/integrations.ts b/src/hooks/integrations.ts index 60476cd1..cb3dc9ef 100644 --- a/src/hooks/integrations.ts +++ b/src/hooks/integrations.ts @@ -288,8 +288,9 @@ export const codex: Integration = { : `"${binaryPath}" --hook ${eventType} --cli codex`; return { type: "command", - // Codex reads `timeout` in SECONDS (its `timeout_sec` field, default 600) — - // like Claude/Cursor/Copilot, and unlike Gemini, whose `timeout` is in + // Codex reads `timeout` in SECONDS (the field is literally `timeout`, + // default 600 per https://developers.openai.com/codex/hooks) — same unit as + // Claude/Cursor/Copilot, and unlike Gemini, whose `timeout` is in // milliseconds (default 60000). 60 = 60s, not 60000ms (~16.7h). command, timeout: 60,