Hook lifecycle gap: no observability between PreToolUse and PostToolUse (stuck tool detection)

Resolved 💬 5 comments Opened Apr 4, 2026 by twhitteberry Closed May 23, 2026

Problem

There is a complete observability blackout between the PreToolUse hook (tool approved) and the PostToolUse hook (tool completed). During this gap:

  • No event fires to indicate a tool has started executing
  • No heartbeat or progress signal exists during execution
  • No way to distinguish between "tool is running normally" vs "tool is stuck/hanging"
  • This is especially severe inside subagents, where the parent collapses activity into +N more tool uses

Real-world scenario

Running a multi-feature lifecycle (/lifecycle V09) that spawns subagents to execute plans. A subagent calls Edit on a file. The edit appears to hang — 15+ minutes with no output, no status change, no indication of progress. The user has no way to know if:

  1. Claude is still thinking (extended thinking before the tool call)
  2. The tool is executing but slow
  3. The tool or API call is genuinely stuck
  4. The subagent has crashed silently

The only recourse is to use BTW (interrupt) to ask "are you still working?" — which disrupts the execution flow.

What exists today

| Hook | When it fires | What it tells you |
|------|--------------|-------------------|
| PreToolUse | Before user approval | Tool is about to be called |
| (nothing) | During execution | Complete blackout |
| PostToolUse | After completion | Tool finished, here's the result |

What's needed

A ToolExecutionStart event or DuringToolUse heartbeat that fires when a tool begins executing (after approval, before completion). This would enable:

  1. Stuck detection — if ToolExecutionStart fired 5 minutes ago with no PostToolUse, something is wrong
  2. Progress tracking — external monitoring tools can show "Edit running on ClueService.cs for 3m"
  3. Subagent visibility — parent process can surface what tools subagents are currently running
  4. Timeout implementation — users could set per-tool timeouts based on execution start time

Proposed solutions (any would help)

Option A: ToolExecutionStart hook — fires immediately after approval, before execution begins. Payload includes tool name, parameters, and a correlation ID matching the eventual PostToolUse.

Option B: Heartbeat during execution — periodic event (every 30s) during long-running tool calls. Payload: tool name, elapsed time, correlation ID.

Option C: Execution duration in status line — the status line (or a new --verbose stream event) shows the currently-executing tool and a running timer.

Option D: Sub-agent event propagation — at minimum, propagate sub-agent tool events to the parent stream so monitoring tools can observe them (related to #43553).

Related issues

This is the root cause behind several open issues that describe symptoms of the same gap:

  • #43553 — Sub-agent internal events invisible in stream-json
  • #42273 — Zero visibility into background agent execution
  • #43437 — No progress indicator during long-running operations
  • #43342 — MCP tools hang indefinitely with no timeout or feedback
  • #40453 — Generic status messages provide zero signal about actual progress (user quote: "The #1 reason I use BTW is not to add context — it's just to ask 'are you still working?'")
  • #43071 — Cannot distinguish extended thinking from API hangs

Environment

  • Claude Code CLI + Desktop (Windows 11)
  • Opus 4.6 (1M context)
  • Heavy use of subagents via Agent tool for parallel execution
  • Custom PostToolUse hook logging JSONL activity (works great — but only fires after completion)
  • 2-4 parallel Claude Code sessions running simultaneously

Who this affects

Anyone using Claude Code for multi-step autonomous workflows where subagents execute plans, especially:

  • TTKWorkflow-style skill orchestration (/lifecycle, /execute-plans)
  • CI/CD pipelines using --output-format stream-json
  • Teams running background agents with run_in_background: true
  • Any workflow where Claude runs for 10+ minutes unattended

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗