Files
oh-my-opencode/src/hooks/claude-code-hooks/handlers/tool-execute-before-handler.ts
T
YeonGyu-Kim 7a96a167e6 perf(claude-code-hooks): defer config loading until after disabled check
Move loadClaudeHooksConfig and loadPluginExtendedConfig after isHookDisabled check
in both tool-execute-before and tool-execute-after handlers to skip 5 file reads
per tool call when hooks are disabled (C1)
2026-03-18 14:18:49 +09:00

94 lines
2.7 KiB
TypeScript

import type { PluginInput } from "@opencode-ai/plugin"
import { loadClaudeHooksConfig } from "../config"
import { loadPluginExtendedConfig } from "../config-loader"
import {
executePreToolUseHooks,
type PreToolUseContext,
} from "../pre-tool-use"
import { appendTranscriptEntry } from "../transcript"
import { cacheToolInput } from "../tool-input-cache"
import type { PluginConfig } from "../types"
import { isHookDisabled, log } from "../../../shared"
export function createToolExecuteBeforeHandler(ctx: PluginInput, config: PluginConfig) {
return async (
input: { tool: string; sessionID: string; callID: string },
output: { args: Record<string, unknown> },
): Promise<void> => {
if (input.tool.trim() === "todowrite" && typeof output.args.todos === "string") {
let parsed: unknown
try {
parsed = JSON.parse(output.args.todos)
} catch {
throw new Error(
`[todowrite ERROR] Failed to parse todos string as JSON. ` +
`Received: ${
output.args.todos.length > 100
? output.args.todos.slice(0, 100) + "..."
: output.args.todos
} ` +
`Expected: Valid JSON array. Pass todos as an array, not a string.`,
)
}
if (!Array.isArray(parsed)) {
throw new Error(
`[todowrite ERROR] Parsed JSON is not an array. ` +
`Received type: ${typeof parsed}. ` +
`Expected: Array of todo objects. Pass todos as [{id, content, status, priority}, ...].`,
)
}
output.args.todos = parsed
log("todowrite: parsed todos string to array", { sessionID: input.sessionID })
}
appendTranscriptEntry(input.sessionID, {
type: "tool_use",
timestamp: new Date().toISOString(),
tool_name: input.tool,
tool_input: output.args,
})
cacheToolInput(input.sessionID, input.tool, input.callID, output.args)
if (isHookDisabled(config, "PreToolUse")) {
return
}
const claudeConfig = await loadClaudeHooksConfig()
const extendedConfig = await loadPluginExtendedConfig()
const preCtx: PreToolUseContext = {
sessionId: input.sessionID,
toolName: input.tool,
toolInput: output.args,
cwd: ctx.directory,
toolUseId: input.callID,
}
const result = await executePreToolUseHooks(preCtx, claudeConfig, extendedConfig)
if (result.decision === "deny") {
ctx.client.tui
.showToast({
body: {
title: "PreToolUse Hook Executed",
message: `[BLOCKED] ${result.toolName ?? input.tool} ${
result.hookName ?? "hook"
}: ${result.elapsedMs ?? 0}ms\n${result.inputLines ?? ""}`,
variant: "error" as const,
duration: 4000,
},
})
.catch(() => {})
throw new Error(result.reason ?? "Hook blocked the operation")
}
if (result.modifiedInput) {
Object.assign(output.args, result.modifiedInput)
}
}
}