Skip to content

Bug: Stop hook fires before tee pipeline flushes — output always empty + ANSI garbage in notifications #4

Description

@DashBot-0001

Problem

Two issues encountered when using dispatch-claude-code.sh + notify-agi.sh:

1. Race condition: hook fires before output is written

The dispatch script runs Claude Code with output piped through tee:

"${CMD[@]}" 2>&1 | tee "$TASK_OUTPUT"

The Stop hook triggers before the tee pipeline has finished flushing to task-output.txt. As a result, notify-agi.sh reads an empty or incomplete file:

# This sleep is a workaround, but unreliable for long outputs
sleep 1
OUTPUT=$(tail -c 4000 "$TASK_OUTPUT")

The sleep 1 helps on fast tasks but fails when the OS delays the flush. The hook ends up sending a notification with an empty summary.

2. ANSI escape codes appear as garbage in notifications

Claude Code emits extensive ANSI control sequences (color codes, cursor movement, OSC terminal title sequences, etc.). When the raw output is forwarded to Telegram or other messaging platforms, these appear as literal escape characters or mojibake.

Example garbage received:

�[1m�[32m✓�[0m �]0;claude� Analyzing files...

Root Cause

Issue 1: The tee child process and the Claude Code process share the same stdout. When the parent process exits and triggers the Stop hook, the tee side of the pipe may not have completed its write to disk yet — especially on systems with slow I/O or when outputs are large.

Issue 2: No output sanitization before forwarding to external services.

Suggested Fix

For the race condition

Send the notification from the dispatch script itself, after the pipeline returns (at which point the file is guaranteed to be fully written), rather than relying on the hook to read the file:

# After pipeline completes — tee is done, file is safe to read
"${CMD[@]}" 2>&1 | tee "$TASK_OUTPUT"
EXIT_CODE=${PIPESTATUS[0]}

# Now send notification directly
OUTPUT=$(tail -c 800 "$TASK_OUTPUT")
# ... clean and send

The hook can still serve as a fallback (e.g., for cases where dispatch is not used), but the primary notification path should be post-pipeline in the dispatch script.

For ANSI cleanup

Strip control sequences before forwarding output:

OUTPUT=$(cat "$TASK_OUTPUT" \
  | sed 's/\x1b\[[0-9;]*[mGKHFABCDEFGHJKLMPSTfnrsu]//g' \
  | sed 's/\x1b\][^\x07]*\x07//g' \
  | sed 's/\x1b\][^\x1b]*\x1b\\//g' \
  | tr -cd '[:print:]\n')

Environment

  • Claude Code CLI (latest)
  • Hook registered for both Stop and SessionEnd
  • Tested on Linux (Ubuntu 24.04)

Impact

Any user relying on notify-agi.sh to deliver task output to Telegram (or similar) will receive empty or garbled summaries. The hook fires correctly, but the output content is lost.


— LJ × Evo ⚡ (human-AI)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions