HookStack
Back to catalogue
ContextPostToolUseFailure

Structured tool failure log

Every tool failure logged for the post-mortem

Records every tool failure into a structured JSON file with timestamp, tool name, input and error, for debugging and auditing.

What does the Structured tool failure log hook do?

Structured tool failure log is a Claude Code PostToolUseFailure hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Every tool failure logged for the post-mortem.

As a PostToolUseFailure hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.

Use cases

  • Debug tools that fail during autonomous sessions
  • Detect recurring failure patterns

Tags

#logging#debugging#failure#audit#error-tracking

settings.json fragment

{
  "hooks": {
    "PostToolUseFailure": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-failure-log.mjs",
            "type": "command"
          }
        ],
        "matcher": ""
      }
    ]
  }
}

Script · .claude/hooks/post-tool-failure-log.mjs

#!/usr/bin/env node
// @hookstack post-tool-failure-log
// Journalise les échecs d'outils pour le débogage (PostToolUseFailure)
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

export function run(
	input,
	{
		append = appendFileSync,
		mkdir = mkdirSync,
		projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd(),
		now = () => new Date().toISOString(),
	} = {},
) {
	const logDir = join(projectDir, ".claude", "data");
	mkdir(logDir, { recursive: true });

	const entry = {
		ts: now(),
		tool: input.tool_name ?? "unknown",
		input: input.tool_input ?? {},
		error: input.error ?? input.tool_response ?? null,
	};

	append(join(logDir, "tool-failures.jsonl"), `${JSON.stringify(entry)}\n`);
	return {
		entry,
		message: `[post-tool-failure] Échec journalisé : ${entry.tool}\n`,
	};
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const input = JSON.parse(readFileSync(0, "utf8"));
	const result = run(input);
	process.stderr.write(result.message);
}

Learn more

Related hooks