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:

  1. Pre-execution guard: reject “detach and forget” command patterns
  2. Post-execution watcher: if a managed background session starts, monitor it until completion
  3. Stop-time check: before a response ends, check for just-finished jobs and force surfacing
  4. Next-prompt injection: include completed outputs in the next user prompt context
  5. 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()