From bbe08f0eef83f4fb2960c36b686f19a02c751683 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 7 Feb 2026 13:12:18 +0900 Subject: [PATCH] fix(hooks): add defensive null check for matcher.hooks to prevent Windows crash (#441) --- bun.lock | 28 +++++++++---------- src/hooks/claude-code-hooks/config.ts | 2 +- src/hooks/claude-code-hooks/post-tool-use.ts | 9 +++--- src/hooks/claude-code-hooks/pre-compact.ts | 9 +++--- src/hooks/claude-code-hooks/pre-tool-use.ts | 9 +++--- src/hooks/claude-code-hooks/stop.ts | 9 +++--- .../claude-code-hooks/user-prompt-submit.ts | 7 +++-- 7 files changed, 39 insertions(+), 34 deletions(-) diff --git a/bun.lock b/bun.lock index b2412a61e..c65491b02 100644 --- a/bun.lock +++ b/bun.lock @@ -28,13 +28,13 @@ "typescript": "^5.7.3", }, "optionalDependencies": { - "oh-my-opencode-darwin-arm64": "3.2.3", - "oh-my-opencode-darwin-x64": "3.2.3", - "oh-my-opencode-linux-arm64": "3.2.3", - "oh-my-opencode-linux-arm64-musl": "3.2.3", - "oh-my-opencode-linux-x64": "3.2.3", - "oh-my-opencode-linux-x64-musl": "3.2.3", - "oh-my-opencode-windows-x64": "3.2.3", + "oh-my-opencode-darwin-arm64": "3.2.4", + "oh-my-opencode-darwin-x64": "3.2.4", + "oh-my-opencode-linux-arm64": "3.2.4", + "oh-my-opencode-linux-arm64-musl": "3.2.4", + "oh-my-opencode-linux-x64": "3.2.4", + "oh-my-opencode-linux-x64-musl": "3.2.4", + "oh-my-opencode-windows-x64": "3.2.4", }, }, }, @@ -226,19 +226,19 @@ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - "oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.2.3", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-Doc9xQCj5Jmx3PzouBIfvDwmfWM94Y9Q9IngFqOjrVpfBef9V/WIH0PlhJU6ps4BKGey8Nf2afFq3UE06Z63Hg=="], + "oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.2.4", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-6vG49R/nkbZYhAqN2oStA+8reZRo2KPPHSbhQd4htdEpzS4ipVz6pW/YTj/TDwunQO7hy66AhP9hOR4pJcoDeA=="], - "oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.2.3", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-w7lO0Hn/AlLCHe33KPbje83Js2h5weDWVMuopEs6d3pi/1zkRDBEhCi63S4J0d0EKod9kEPQA6ojtdVJ4J39zQ=="], + "oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.2.4", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-Utfpclg8xHj93+faX2L4dpkzhM6D58YEtjkVlHq4CxZ8MdpYCs2l4NtY/b9T1GWmtQWFxZQhmIdAcwe1qApgpQ=="], - "oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.2.3", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-m1tS1jRLO2Svm5NuetK3BAgdAR8b2GkiIfMFoIYsLJTPmzIkXaigAYkFq+BXCs5JAbRmPmvjndz9cuCddnPADQ=="], + "oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.2.4", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-z4Zlvt1a1PSQVprbgx6bLOeNuILX4d9p80GrTWuuYzqY+OEgbb74LVVUFCsvt8UgnhRTnHuhmphSpIL7UznzZg=="], - "oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.2.3", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-Q/0AGtOuUFGNGIX8F6iD5W8c2spbjrqVBPt0B7laQSwnScKs/BI+TvM6HRE37vhoWg+fzhAX3QYJ2H9Un9FYrg=="], + "oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.2.4", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-pCCPM8rsuwMR3a7XIDyYyr/D1HkMPffOYGXeOY8vBaLL8NKFl8d0H5twA3HIiEqcDINHV3kw9zteL2paW+mHSQ=="], - "oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.2.3", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-RIAyoj2XbT8vH++5fPUkdO+D1tfqxh+iWto7CqWr1TgbABbBJljGk91HJgS9xjnxyCQJEpFhTmO7NMHKJcZOWQ=="], + "oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.2.4", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-vU9l4rS1oRpCgyXalBiUOOFPddIwSmuWoGY1PgO4dr6Db+gtEpmaDpLcEi5j4jFUDRLH6btQvNAp/eAydVgOJQ=="], - "oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.2.3", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-nnQK3y7R4DrBvqdqRGbujL2oAAQnVVb23JHUbJPQ6YxrRRGWpLOVGvK5c16ykSFEUPl8eZDmi1ON/R4opKLOUw=="], + "oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.2.4", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-OZ+yRl7tOXoWTHh7zQ8WsTasKqZaIaVO3QeUQhDIS5JXFjbgjMgFeC/XBegsCgfqglWTOlMatmCO1S3nx2vy2w=="], - "oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.2.3", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-mt8E/TkpaCp04pvzwntT8x8TaqXDt3zCD5X2eA8ZZMrb5ofNr5HyG5G4SFXrUh+Ez3b/3YXpNWv6f6rnAlk1Dg=="], + "oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.2.4", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-W6TX8OiPCOmu7UZgZESh5DSWat0zH/6WPC3tdvjzwYnik9ZvRiyJGHh9B4uAG3DdqTC+pZJrpuTq1NctqMJiDA=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], diff --git a/src/hooks/claude-code-hooks/config.ts b/src/hooks/claude-code-hooks/config.ts index 60f9bc27a..3a03d200b 100644 --- a/src/hooks/claude-code-hooks/config.ts +++ b/src/hooks/claude-code-hooks/config.ts @@ -20,7 +20,7 @@ interface RawClaudeHooksConfig { function normalizeHookMatcher(raw: RawHookMatcher): HookMatcher { return { matcher: raw.matcher ?? raw.pattern ?? "*", - hooks: raw.hooks, + hooks: Array.isArray(raw.hooks) ? raw.hooks : [], } } diff --git a/src/hooks/claude-code-hooks/post-tool-use.ts b/src/hooks/claude-code-hooks/post-tool-use.ts index abfcf4480..31b88dc06 100644 --- a/src/hooks/claude-code-hooks/post-tool-use.ts +++ b/src/hooks/claude-code-hooks/post-tool-use.ts @@ -91,11 +91,12 @@ export async function executePostToolUseHooks( const startTime = Date.now() - for (const matcher of matchers) { - for (const hook of matcher.hooks) { - if (hook.type !== "command") continue + for (const matcher of matchers) { + if (!matcher.hooks || matcher.hooks.length === 0) continue + for (const hook of matcher.hooks) { + if (hook.type !== "command") continue - if (isHookCommandDisabled("PostToolUse", hook.command, extendedConfig ?? null)) { + if (isHookCommandDisabled("PostToolUse", hook.command, extendedConfig ?? null)) { log("PostToolUse hook command skipped (disabled by config)", { command: hook.command, toolName: ctx.toolName }) continue } diff --git a/src/hooks/claude-code-hooks/pre-compact.ts b/src/hooks/claude-code-hooks/pre-compact.ts index 17a4b58e8..e2d877396 100644 --- a/src/hooks/claude-code-hooks/pre-compact.ts +++ b/src/hooks/claude-code-hooks/pre-compact.ts @@ -47,11 +47,12 @@ export async function executePreCompactHooks( let firstHookName: string | undefined const collectedContext: string[] = [] - for (const matcher of matchers) { - for (const hook of matcher.hooks) { - if (hook.type !== "command") continue + for (const matcher of matchers) { + if (!matcher.hooks || matcher.hooks.length === 0) continue + for (const hook of matcher.hooks) { + if (hook.type !== "command") continue - if (isHookCommandDisabled("PreCompact", hook.command, extendedConfig ?? null)) { + if (isHookCommandDisabled("PreCompact", hook.command, extendedConfig ?? null)) { log("PreCompact hook command skipped (disabled by config)", { command: hook.command }) continue } diff --git a/src/hooks/claude-code-hooks/pre-tool-use.ts b/src/hooks/claude-code-hooks/pre-tool-use.ts index ac362a8e7..2b5a33c5c 100644 --- a/src/hooks/claude-code-hooks/pre-tool-use.ts +++ b/src/hooks/claude-code-hooks/pre-tool-use.ts @@ -74,11 +74,12 @@ export async function executePreToolUseHooks( let firstHookName: string | undefined const inputLines = buildInputLines(ctx.toolInput) - for (const matcher of matchers) { - for (const hook of matcher.hooks) { - if (hook.type !== "command") continue + for (const matcher of matchers) { + if (!matcher.hooks || matcher.hooks.length === 0) continue + for (const hook of matcher.hooks) { + if (hook.type !== "command") continue - if (isHookCommandDisabled("PreToolUse", hook.command, extendedConfig ?? null)) { + if (isHookCommandDisabled("PreToolUse", hook.command, extendedConfig ?? null)) { log("PreToolUse hook command skipped (disabled by config)", { command: hook.command, toolName: ctx.toolName }) continue } diff --git a/src/hooks/claude-code-hooks/stop.ts b/src/hooks/claude-code-hooks/stop.ts index d1e1e8d18..9792eb65c 100644 --- a/src/hooks/claude-code-hooks/stop.ts +++ b/src/hooks/claude-code-hooks/stop.ts @@ -65,11 +65,12 @@ export async function executeStopHooks( hook_source: "opencode-plugin", } - for (const matcher of matchers) { - for (const hook of matcher.hooks) { - if (hook.type !== "command") continue + for (const matcher of matchers) { + if (!matcher.hooks || matcher.hooks.length === 0) continue + for (const hook of matcher.hooks) { + if (hook.type !== "command") continue - if (isHookCommandDisabled("Stop", hook.command, extendedConfig ?? null)) { + if (isHookCommandDisabled("Stop", hook.command, extendedConfig ?? null)) { log("Stop hook command skipped (disabled by config)", { command: hook.command }) continue } diff --git a/src/hooks/claude-code-hooks/user-prompt-submit.ts b/src/hooks/claude-code-hooks/user-prompt-submit.ts index b358ef392..1b80a076b 100644 --- a/src/hooks/claude-code-hooks/user-prompt-submit.ts +++ b/src/hooks/claude-code-hooks/user-prompt-submit.ts @@ -70,9 +70,10 @@ export async function executeUserPromptSubmitHooks( hook_source: "opencode-plugin", } - for (const matcher of matchers) { - for (const hook of matcher.hooks) { - if (hook.type !== "command") continue + for (const matcher of matchers) { + if (!matcher.hooks || matcher.hooks.length === 0) continue + for (const hook of matcher.hooks) { + if (hook.type !== "command") continue if (isHookCommandDisabled("UserPromptSubmit", hook.command, extendedConfig ?? null)) { log("UserPromptSubmit hook command skipped (disabled by config)", { command: hook.command })