fix(hooks): merge marketplace plugin hooksConfigs into claude-code-hooks at config time
Previously, loadPluginHooksConfigs() loaded plugin hooks from marketplace plugins (hookify, superpowers, zzcommon, zzfe, etc.) into pluginComponents.hooksConfigs, but config-handler.ts never consumed them. This meant plugin hooks were discovered but never merged into the runtime hooks dispatch system. Changes: - Extend ClaudeHookEvent and ClaudeHooksConfig to support all 12 event types (PostToolUseFailure, PermissionRequest, Notification, SubagentStart, SubagentStop, SessionStart, SessionEnd) in addition to the existing 5 - Add ALL_HOOK_EVENT_TYPES constant as single source of truth for event type iteration - Add mergePluginHooksConfigs() to unwrap plugin HooksConfig (with hooks wrapper) into flat ClaudeHooksConfig, filtering out unsupported prompt/agent hook types - Add setPluginHooksConfigs() to store pending plugin configs and invalidate the config cache - Create applyHookConfig() handler following existing applyXxxConfig pattern, wired into config-handler after loadPluginComponents() - Extend DisabledHooksConfig and mergeDisabledHooks for all 12 events Closes #4179
This commit is contained in:
@@ -11,7 +11,14 @@ export interface DisabledHooksConfig {
|
||||
Stop?: string[]
|
||||
PreToolUse?: string[]
|
||||
PostToolUse?: string[]
|
||||
PostToolUseFailure?: string[]
|
||||
PermissionRequest?: string[]
|
||||
UserPromptSubmit?: string[]
|
||||
Notification?: string[]
|
||||
SubagentStart?: string[]
|
||||
SubagentStop?: string[]
|
||||
SessionStart?: string[]
|
||||
SessionEnd?: string[]
|
||||
PreCompact?: string[]
|
||||
}
|
||||
|
||||
@@ -78,10 +85,17 @@ function mergeDisabledHooks(
|
||||
if (!base) return override
|
||||
|
||||
return {
|
||||
Stop: override.Stop ?? base.Stop,
|
||||
PreToolUse: override.PreToolUse ?? base.PreToolUse,
|
||||
PostToolUse: override.PostToolUse ?? base.PostToolUse,
|
||||
PostToolUseFailure: override.PostToolUseFailure ?? base.PostToolUseFailure,
|
||||
PermissionRequest: override.PermissionRequest ?? base.PermissionRequest,
|
||||
UserPromptSubmit: override.UserPromptSubmit ?? base.UserPromptSubmit,
|
||||
Notification: override.Notification ?? base.Notification,
|
||||
Stop: override.Stop ?? base.Stop,
|
||||
SubagentStart: override.SubagentStart ?? base.SubagentStart,
|
||||
SubagentStop: override.SubagentStop ?? base.SubagentStop,
|
||||
SessionStart: override.SessionStart ?? base.SessionStart,
|
||||
SessionEnd: override.SessionEnd ?? base.SessionEnd,
|
||||
PreCompact: override.PreCompact ?? base.PreCompact,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,33 @@ interface RawHookMatcher {
|
||||
interface RawClaudeHooksConfig {
|
||||
PreToolUse?: RawHookMatcher[]
|
||||
PostToolUse?: RawHookMatcher[]
|
||||
PostToolUseFailure?: RawHookMatcher[]
|
||||
PermissionRequest?: RawHookMatcher[]
|
||||
UserPromptSubmit?: RawHookMatcher[]
|
||||
Notification?: RawHookMatcher[]
|
||||
Stop?: RawHookMatcher[]
|
||||
SubagentStart?: RawHookMatcher[]
|
||||
SubagentStop?: RawHookMatcher[]
|
||||
SessionStart?: RawHookMatcher[]
|
||||
SessionEnd?: RawHookMatcher[]
|
||||
PreCompact?: RawHookMatcher[]
|
||||
}
|
||||
|
||||
const ALL_HOOK_EVENT_TYPES: (keyof ClaudeHooksConfig)[] = [
|
||||
"PreToolUse",
|
||||
"PostToolUse",
|
||||
"PostToolUseFailure",
|
||||
"PermissionRequest",
|
||||
"UserPromptSubmit",
|
||||
"Notification",
|
||||
"Stop",
|
||||
"SubagentStart",
|
||||
"SubagentStop",
|
||||
"SessionStart",
|
||||
"SessionEnd",
|
||||
"PreCompact",
|
||||
]
|
||||
|
||||
function normalizeHookMatcher(raw: RawHookMatcher): HookMatcher {
|
||||
return {
|
||||
matcher: raw.matcher ?? raw.pattern ?? "*",
|
||||
@@ -36,15 +58,8 @@ function normalizeHookMatcher(raw: RawHookMatcher): HookMatcher {
|
||||
|
||||
function normalizeHooksConfig(raw: RawClaudeHooksConfig): ClaudeHooksConfig {
|
||||
const result: ClaudeHooksConfig = {}
|
||||
const eventTypes: (keyof RawClaudeHooksConfig)[] = [
|
||||
"PreToolUse",
|
||||
"PostToolUse",
|
||||
"UserPromptSubmit",
|
||||
"Stop",
|
||||
"PreCompact",
|
||||
]
|
||||
|
||||
for (const eventType of eventTypes) {
|
||||
for (const eventType of ALL_HOOK_EVENT_TYPES) {
|
||||
if (raw[eventType]) {
|
||||
result[eventType] = raw[eventType].map(normalizeHookMatcher)
|
||||
}
|
||||
@@ -97,14 +112,7 @@ function mergeHooksConfig(
|
||||
override: ClaudeHooksConfig
|
||||
): ClaudeHooksConfig {
|
||||
const result: ClaudeHooksConfig = { ...base }
|
||||
const eventTypes: (keyof ClaudeHooksConfig)[] = [
|
||||
"PreToolUse",
|
||||
"PostToolUse",
|
||||
"UserPromptSubmit",
|
||||
"Stop",
|
||||
"PreCompact",
|
||||
]
|
||||
for (const eventType of eventTypes) {
|
||||
for (const eventType of ALL_HOOK_EVENT_TYPES) {
|
||||
if (override[eventType]) {
|
||||
result[eventType] = [...(base[eventType] || []), ...override[eventType]]
|
||||
}
|
||||
@@ -112,6 +120,64 @@ function mergeHooksConfig(
|
||||
return result
|
||||
}
|
||||
|
||||
let pendingPluginHooksConfigs: Array<{ hooks?: Record<string, unknown> }> = []
|
||||
|
||||
export function setPluginHooksConfigs(configs: Array<{ hooks?: Record<string, unknown> }>): void {
|
||||
pendingPluginHooksConfigs = configs
|
||||
configCache.clear()
|
||||
}
|
||||
|
||||
function isHookAction(h: unknown): h is HookAction {
|
||||
if (typeof h !== "object" || h === null) return false
|
||||
const obj = h as Record<string, unknown>
|
||||
if (obj.type === "command" && typeof obj.command === "string") return true
|
||||
if (obj.type === "http" && typeof obj.url === "string") return true
|
||||
return false
|
||||
}
|
||||
|
||||
interface PluginHookMatcher {
|
||||
matcher?: string
|
||||
pattern?: string
|
||||
hooks?: unknown[]
|
||||
}
|
||||
|
||||
function isPluginHookMatcher(m: unknown): m is PluginHookMatcher {
|
||||
return typeof m === "object" && m !== null && Array.isArray((m as PluginHookMatcher).hooks)
|
||||
}
|
||||
|
||||
export function mergePluginHooksConfigs(
|
||||
base: ClaudeHooksConfig,
|
||||
pluginHooksConfigs: Array<{ hooks?: Record<string, unknown> }>
|
||||
): ClaudeHooksConfig {
|
||||
let result = { ...base }
|
||||
|
||||
for (const pluginConfig of pluginHooksConfigs) {
|
||||
if (!pluginConfig.hooks) continue
|
||||
|
||||
const pluginOverrides: ClaudeHooksConfig = {}
|
||||
for (const eventType of ALL_HOOK_EVENT_TYPES) {
|
||||
const pluginMatchers = pluginConfig.hooks[eventType]
|
||||
if (!Array.isArray(pluginMatchers)) continue
|
||||
|
||||
const converted: HookMatcher[] = pluginMatchers
|
||||
.filter(isPluginHookMatcher)
|
||||
.map((m) => ({
|
||||
matcher: m.matcher ?? m.pattern ?? "*",
|
||||
hooks: (m.hooks ?? []).filter(isHookAction),
|
||||
}))
|
||||
.filter((m) => m.hooks.length > 0)
|
||||
|
||||
if (converted.length > 0) {
|
||||
pluginOverrides[eventType] = converted
|
||||
}
|
||||
}
|
||||
|
||||
result = mergeHooksConfig(result, pluginOverrides)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function loadClaudeHooksConfig(
|
||||
customSettingsPath?: string
|
||||
): Promise<ClaudeHooksConfig | null> {
|
||||
@@ -139,6 +205,11 @@ export async function loadClaudeHooksConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// Merge plugin hooks configs
|
||||
if (pendingPluginHooksConfigs.length > 0) {
|
||||
mergedConfig = mergePluginHooksConfigs(mergedConfig, pendingPluginHooksConfigs)
|
||||
}
|
||||
|
||||
const resolvedConfig = Object.keys(mergedConfig).length > 0 ? mergedConfig : null
|
||||
configCache.set(cacheKey, {
|
||||
value: resolvedConfig,
|
||||
|
||||
@@ -6,8 +6,15 @@
|
||||
export type ClaudeHookEvent =
|
||||
| "PreToolUse"
|
||||
| "PostToolUse"
|
||||
| "PostToolUseFailure"
|
||||
| "PermissionRequest"
|
||||
| "UserPromptSubmit"
|
||||
| "Notification"
|
||||
| "Stop"
|
||||
| "SubagentStart"
|
||||
| "SubagentStop"
|
||||
| "SessionStart"
|
||||
| "SessionEnd"
|
||||
| "PreCompact"
|
||||
|
||||
export interface HookMatcher {
|
||||
@@ -33,8 +40,15 @@ export type HookAction = HookCommand | HookHttp
|
||||
export interface ClaudeHooksConfig {
|
||||
PreToolUse?: HookMatcher[]
|
||||
PostToolUse?: HookMatcher[]
|
||||
PostToolUseFailure?: HookMatcher[]
|
||||
PermissionRequest?: HookMatcher[]
|
||||
UserPromptSubmit?: HookMatcher[]
|
||||
Notification?: HookMatcher[]
|
||||
Stop?: HookMatcher[]
|
||||
SubagentStart?: HookMatcher[]
|
||||
SubagentStop?: HookMatcher[]
|
||||
SessionStart?: HookMatcher[]
|
||||
SessionEnd?: HookMatcher[]
|
||||
PreCompact?: HookMatcher[]
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ModelCacheState } from "../plugin-state";
|
||||
import { log } from "../shared";
|
||||
import { applyAgentConfig } from "./agent-config-handler";
|
||||
import { applyCommandConfig } from "./command-config-handler";
|
||||
import { applyHookConfig } from "./hook-config-handler";
|
||||
import { applyMcpConfig } from "./mcp-config-handler";
|
||||
import { applyProviderConfig } from "./provider-config-handler";
|
||||
import { loadPluginComponents } from "./plugin-components-loader";
|
||||
@@ -30,6 +31,8 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
|
||||
const pluginComponents = await loadPluginComponents({ pluginConfig });
|
||||
|
||||
applyHookConfig({ pluginComponents });
|
||||
|
||||
const agentResult = await applyAgentConfig({
|
||||
config,
|
||||
pluginConfig,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { PluginComponents } from "./plugin-components-loader"
|
||||
import { setPluginHooksConfigs } from "../hooks/claude-code-hooks/config"
|
||||
import { log } from "../shared"
|
||||
|
||||
export function applyHookConfig(params: {
|
||||
pluginComponents: PluginComponents;
|
||||
}): void {
|
||||
const { pluginComponents } = params
|
||||
|
||||
if (pluginComponents.hooksConfigs.length > 0) {
|
||||
log("[hook-config-handler] Merging plugin hooks configs", {
|
||||
count: pluginComponents.hooksConfigs.length,
|
||||
plugins: pluginComponents.plugins.map(p => p.name),
|
||||
})
|
||||
|
||||
setPluginHooksConfigs(pluginComponents.hooksConfigs)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export * from "./provider-config-handler";
|
||||
export * from "./agent-config-handler";
|
||||
export * from "./tool-config-handler";
|
||||
export * from "./mcp-config-handler";
|
||||
export * from "./hook-config-handler";
|
||||
export * from "./command-config-handler";
|
||||
export * from "./plugin-components-loader";
|
||||
export * from "./category-config-resolver";
|
||||
|
||||
Reference in New Issue
Block a user