Making Claude Code background tasks observable
Claude Code can run commands in the background, but the output isn’t reliably surfaced. A task can finish or fail and the assistant won’t know unless you manually check. Background work that completes but is never acknowledged is effectively invisible.
The failure modes
- Background work completes but doesn’t appear in the next interaction
- Failure output arrives late or out of order
- No lifecycle from “started” to “acknowledged by the assistant”
- Manual polling to confirm whether something succeeded
A hook-driven pipeline
I’m using Claude Code hooks to enforce a five-stage execution path:
- Pre-execution guard: reject “detach and forget” command patterns
- Post-execution watcher: if a managed background session starts, monitor it until completion
- Stop-time check: before a response ends, check for just-finished jobs and force surfacing
- Next-prompt injection: include completed outputs in the next user prompt context
- Session cleanup: remove stale/dead sessions at next startup
The loop is: launch, track, capture, surface, acknowledge.
Pseudocode
on_pre_tool_use(command):
if command contains detached_patterns:
deny_with_guidance()
else:
allow()
on_post_tool_use(result):
if managed_session_created(result):
start_async_watcher(session_id)
watcher(session_id):
wait_for_session_to_exist(timeout_short)
while not all_panes_dead(session_id) and elapsed < max_wait:
sleep(poll_interval)
output = capture_session_output(session_id)
save(output, state_dir/session_id.done)
on_stop_event():
for session in managed_sessions:
if all_panes_dead(session) and not already_seen(session):
output = capture_session_output(session)
failed = matches_failure_signals(output, exit_code(session))
emit_forced_result_block(session, output, failed)
mark_seen(session)
on_user_prompt_submit():
for done_file in unread_done_files:
inject_output_into_prompt_context(done_file)
mark_as_read(done_file)
include_retained_references_for_recent_reads()
on_session_start():
cleanup_dead_sessions_and_markers()