mirror of
https://github.com/lukaszraczylo/claude-adam.git
synced 2026-10-12 04:55:30 +00:00
feat(observe): record real tool failures from PostToolUseFailure
Claude Code sends failed tool calls to the PostToolUseFailure event. ADAM registered only PostToolUse, so it never saw a real failure, and every tool_error_loop came from ERROR_RE matching successful output. Handle PostToolUseFailure in adam-observe.mjs through the PostToolUse detectors. Shape the error string as an is_error response. An interrupt is an abort, so it counts as a tool event but gets no fingerprint. Register the event in settings.json.example. Tests 9b and 9c added: 150 passed, 0 failed. Claude-Session: https://claude.ai/code/session_014prTNuJnKuwcAd28vUZAnN
This commit is contained in:
@@ -187,6 +187,25 @@ for i in 1 2 3; do
|
|||||||
done
|
done
|
||||||
assert_grep "$ROOT/journal.jsonl" '"type":"tool_error_loop"' "3x same error logs tool_error_loop"
|
assert_grep "$ROOT/journal.jsonl" '"type":"tool_error_loop"' "3x same error logs tool_error_loop"
|
||||||
|
|
||||||
|
# --- Test 9b: PostToolUseFailure feeds the same detectors ---
|
||||||
|
echo "Test 9b: 3x PostToolUseFailure with the same error logs tool_error_loop"
|
||||||
|
reset_state
|
||||||
|
for i in 1 2 3; do
|
||||||
|
echo '{"hook_event_name":"PostToolUseFailure","tool_name":"Bash","tool_input":{"command":"npm test"},"error":"Exit code 1\nError: Cannot find module express","is_interrupt":false,"session_id":"s9b","cwd":"/tmp/x"}' \
|
||||||
|
| HOOK_RUN >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
assert_grep "$ROOT/journal.jsonl" '"type":"tool_error_loop"' "3x PostToolUseFailure logs tool_error_loop"
|
||||||
|
|
||||||
|
# --- Test 9c: interrupted tool calls are not errors ---
|
||||||
|
echo "Test 9c: 8x PostToolUseFailure with is_interrupt:true counts as tool use but logs no tool_error_loop"
|
||||||
|
reset_state
|
||||||
|
for i in 1 2 3 4 5 6 7 8; do
|
||||||
|
echo '{"hook_event_name":"PostToolUseFailure","tool_name":"Bash","tool_input":{"command":"sleep 99"},"error":"Error: aborted","is_interrupt":true,"session_id":"s9c","cwd":"/tmp/x"}' \
|
||||||
|
| HOOK_RUN >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
assert_grep "$ROOT/journal.jsonl" '"type":"dead_end"' "interrupted calls still count as tool events"
|
||||||
|
assert_no_grep "$ROOT/journal.jsonl" '"type":"tool_error_loop"' "interrupts do not log tool_error_loop"
|
||||||
|
|
||||||
# --- Test 10: dead_end on long autonomous run ---
|
# --- Test 10: dead_end on long autonomous run ---
|
||||||
echo "Test 10: dead_end after 8 tools without UserPromptSubmit"
|
echo "Test 10: dead_end after 8 tools without UserPromptSubmit"
|
||||||
reset_state
|
reset_state
|
||||||
|
|||||||
+10
-5
@@ -465,12 +465,17 @@ function main() {
|
|||||||
state.subagent_dispatch_emitted[name] = true;
|
state.subagent_dispatch_emitted[name] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (event === "PostToolUse") {
|
} else if (event === "PostToolUse" || event === "PostToolUseFailure") {
|
||||||
const tool = input.tool_name || "unknown";
|
const tool = input.tool_name || "unknown";
|
||||||
const argsHash = djb2(JSON.stringify(input.tool_input || {}));
|
const argsHash = djb2(JSON.stringify(input.tool_input || {}));
|
||||||
const file = (input.tool_input && (input.tool_input.file_path || input.tool_input.path)) || null;
|
const file = (input.tool_input && (input.tool_input.file_path || input.tool_input.path)) || null;
|
||||||
|
|
||||||
const toolResponse = input.tool_response;
|
// Claude Code sends failed tool calls to PostToolUseFailure with the error
|
||||||
|
// text in `error`. Shape it like an is_error response so every PostToolUse
|
||||||
|
// detector sees real failures. An interrupt is an abort, not a tool error.
|
||||||
|
const toolResponse = event === "PostToolUseFailure"
|
||||||
|
? { is_error: input.is_interrupt !== true, content: String(input.error || "") }
|
||||||
|
: input.tool_response;
|
||||||
const respExcerpt = (() => {
|
const respExcerpt = (() => {
|
||||||
if (!toolResponse) return null;
|
if (!toolResponse) return null;
|
||||||
const text = typeof toolResponse === "string" ? toolResponse
|
const text = typeof toolResponse === "string" ? toolResponse
|
||||||
@@ -541,11 +546,11 @@ function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.tool_response && typeof input.tool_response === "object") {
|
if (toolResponse && typeof toolResponse === "object") {
|
||||||
bumpUsage("payload:tool_response_seen");
|
bumpUsage("payload:tool_response_seen");
|
||||||
}
|
}
|
||||||
|
|
||||||
const fp = errorFingerprint(input.tool_response);
|
const fp = errorFingerprint(toolResponse);
|
||||||
if (fp) {
|
if (fp) {
|
||||||
bumpUsage("payload:tool_response_error_seen");
|
bumpUsage("payload:tool_response_error_seen");
|
||||||
if (state.recentRecoveries.length) {
|
if (state.recentRecoveries.length) {
|
||||||
@@ -590,7 +595,7 @@ function main() {
|
|||||||
if (tool === "Bash") {
|
if (tool === "Bash") {
|
||||||
const cmd = (input.tool_input && input.tool_input.command) || "";
|
const cmd = (input.tool_input && input.tool_input.command) || "";
|
||||||
const isBuildCmd = BUILD_RE.test(cmd);
|
const isBuildCmd = BUILD_RE.test(cmd);
|
||||||
const hasError = (input.tool_response && input.tool_response.is_error === true) || fp !== null;
|
const hasError = (toolResponse && toolResponse.is_error === true) || fp !== null;
|
||||||
if (isBuildCmd && hasError) {
|
if (isBuildCmd && hasError) {
|
||||||
state.build_failure_count += 1;
|
state.build_failure_count += 1;
|
||||||
if (state.build_failure_count >= BUILD_LOOP_THRESHOLD && !state.build_loop_emitted) {
|
if (state.build_failure_count >= BUILD_LOOP_THRESHOLD && !state.build_loop_emitted) {
|
||||||
|
|||||||
@@ -34,6 +34,17 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"PostToolUseFailure": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "node \"$HOME/.claude/hooks/adam-observe.mjs\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"SessionStart": [
|
"SessionStart": [
|
||||||
{
|
{
|
||||||
"hooks": [
|
"hooks": [
|
||||||
|
|||||||
Reference in New Issue
Block a user