fix(hook): add tool_use/tool_result pair validator to prevent API errors

Adds a defensive tool-pair-validator hook that runs as the final step in
the messages transform pipeline. When compaction or context-window recovery
removes user messages containing tool_result blocks without removing the
preceding assistant message with tool_use blocks, this validator detects
the mismatch and either:

1. Injects missing tool_result parts into the next user message, or
2. Creates a synthetic user message with placeholder tool_results

This prevents Anthropic API errors like 'tool_use ids found without
tool_result blocks immediately after'.

Fixes #3014
This commit is contained in:
YeonGyu-Kim
2026-04-02 13:31:48 +09:00
parent 51d9685571
commit 2440ed9a6f
7 changed files with 358 additions and 0 deletions
@@ -5,6 +5,7 @@ import {
createClaudeCodeHooksHook,
createKeywordDetectorHook,
createThinkingBlockValidatorHook,
createToolPairValidatorHook,
} from "../../hooks"
import {
contextCollector,
@@ -17,6 +18,7 @@ export type TransformHooks = {
keywordDetector: ReturnType<typeof createKeywordDetectorHook> | null
contextInjectorMessagesTransform: ReturnType<typeof createContextInjectorMessagesTransformHook>
thinkingBlockValidator: ReturnType<typeof createThinkingBlockValidatorHook> | null
toolPairValidator: ReturnType<typeof createToolPairValidatorHook> | null
}
export function createTransformHooks(args: {
@@ -63,10 +65,19 @@ export function createTransformHooks(args: {
)
: null
const toolPairValidator = isHookEnabled("tool-pair-validator")
? safeCreateHook(
"tool-pair-validator",
() => createToolPairValidatorHook(),
{ enabled: safeHookEnabled },
)
: null
return {
claudeCodeHooks,
keywordDetector,
contextInjectorMessagesTransform,
thinkingBlockValidator,
toolPairValidator,
}
}
+4
View File
@@ -20,5 +20,9 @@ export function createMessagesTransformHandler(args: {
await args.hooks.thinkingBlockValidator?.[
"experimental.chat.messages.transform"
]?.(input, output)
await args.hooks.toolPairValidator?.[
"experimental.chat.messages.transform"
]?.(input, output)
}
}