Feature Request: PostToolUseFailed hook for bash command failures

Status Closed — not planned
Maintainer reply None cached
Activity 8 comments · opened Jan 4, 2026 · closed Mar 1, 2026

Summary

Add a PostToolUseFailed hook (or extend PostToolUse with failure context) that triggers when a bash command exits with a non-zero exit code.

Use Case

I'm building a "learning flywheel" system where command failures should be captured as lessons learned to prevent repeating mistakes. Currently:

  1. Bash command fails (exit code != 0)
  2. I want to automatically prompt/remind to run a failure capture command
  3. Problem: PostToolUse only fires on successful completion, so failures go undetected by hooks

Current Limitation

From the docs, PostToolUse runs "after a tool completes successfully." There's no hook for when tools fail.

Proposed Solution

Option A: Add PostToolUseFailed event type

{
  "hooks": {
    "PostToolUseFailed": [
      {
        "matcher": { "tool_name": "Bash" },
        "hooks": [{ "type": "command", "command": "./on-failure.sh" }]
      }
    ]
  }
}

Option B: Extend PostToolUse to always fire, with exit code in context

{
  "tool_name": "Bash",
  "exit_code": 1,
  "succeeded": false,
  "output": "..."
}

Impact

This would enable:

  • Automated failure logging/learning systems
  • CI-style failure notifications
  • Quality enforcement workflows
  • Better debugging context preservation

Environment

  • Claude Code CLI
  • macOS/Linux

View original on GitHub ↗

8 Comments

Elijas · 9 months ago

(Consolidating from #16599, originally tracked in #6371)

This supports Option B above—extending PostToolUse to always fire with failure context rather than adding a separate event.

Proposed Payload for Failed Tools

{
  "session_id": "abc123...",
  "hook_event_name": "PostToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "ls nonexistent-file.txt"
  },
  "success": false,
  "failure_details": {
    "exit_code": 2,
    "stdout": "",
    "stderr": "ls: cannot access 'nonexistent-file.txt': No such file or directory",
    "error_type": "CommandFailed"
  }
}

Current behavior: PostToolUse only fires on success; failures skip the hook entirely.

Requested behavior: Fire for all completions. Include "success": true for successful calls (preserving current behavior), "success": false + failure_details for failed calls.

Additional Use Cases

  • Analytics & Logging: Track all tool usage (including failures) to improve MCP server instructions and tool schemas
  • Contextual Error Feedback: Provide Claude with better context (e.g., "file not found - here are files in current directory")
  • Automated Remediation Hints: Parse stderr to suggest fixes (e.g., git push --set-upstream origin branch-name)
  • Cleanup After Failures: Remove temp files/directories created before multi-step operations fail
MarcinDudekDev · 9 months ago

Use case: Detecting repeated tool failures to automatically spawn a "batman" agent that fixes broken tools instead of Claude giving up and trying workarounds.

Current limitation: We attempted to build this with PostToolUse hooks but discovered they only fire on success. Without failure detection, we can't implement "learning from mistakes" workflows.

Preferred solution: Option B (extend PostToolUse with success: boolean field) - simpler API, backwards compatible.

+1 for this feature!

Elijas · 9 months ago

Wider consideration: Claude Code must have escape hatches

Most other Claude Code failures and bugs can be solved with... hooks but not when the hooks themselves are broken

Claude Code will always have bugs and these escape hatches like

  • hooks
  • plaintext-filesystem-as-storage (~/.claude)

are the only fix we have other than spamming Github Issues (since we can't fork Claude Code, can't use Claude Code subscriptions with open source competitors, and the engineering capacity of Anthropic isn't infinite either).

Just a thought - the prioritization of these emergency surfaces would make the dev experience much better @bcherny

This is not a Feature Request, it should be a Bug Report

A hook named PostToolUse semantically implies "after tool use completes" — not "after tool use succeeds." If the intent were success-only, you'd expect it named OnToolSuccess or PostToolUseSuccess, with a companion failure hook from the start.

The current behavior violates the principle of least surprise. Every standard use case for post-event hooks (logging, cleanup, analytics, error handling) requires firing on all completions. A "post" hook that only fires on success isn't really a post-hook — it's a pre-hook for "what to do next."

leighmcculloch · 9 months ago

I ran into this limitation today. My use case is prompting the model to investigate errors before proceeding.

The earlier comments suggest Option B is backwards compatible:

Preferred solution: Option B (extend PostToolUse with success: boolean field) - simpler API, backwards compatible.

I think there's a risk here. Existing PostToolUse hooks were written assuming they only fire on success. If they suddenly start firing on failures too, that could cause problems.

For example, a hook that logs successful commands to a file and then syncs that file to a remote server. If it starts firing on failures, it would sync incomplete logs or trigger unnecessary network requests.

A more concerning example would be a hook that auto-commits after successful test runs. If it suddenly fires on test failures, it could commit broken code.

Two approaches avoid this:

  1. Option A from the original issue (a new PostToolUseFailed hook) sidesteps the problem entirely. Existing hooks remain unchanged.
  1. Option C: Extend PostToolUse with a filter field and a safe default would work:

``json
{
"matcher": "Bash",
"trigger": "failure",
"hooks": [{ "command": "./on-failure.sh" }]
}
``

The trigger field would accept "success" (the default, preserving current behavior), "failure", or "both". Existing configurations without the field would continue working as they do today.

Elijas · 8 months ago

@anthropicbot Yes this issue is still occuring

github-actions[bot] · 7 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

Elijas · 7 months ago
github-actions[bot] · 7 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.