Text streamed in the same response as ScheduleWakeup is destroyed — absent from the session jsonl, not just hidden

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 30, 2026

Summary

Text the model streams in the same API response as a ScheduleWakeup tool call is not just hidden from the UI — it is absent from the session transcript (.jsonl) entirely. The assistant entry that lands in the jsonl contains only the lone ScheduleWakeup tool_use block, with no text block, even when the model streamed a multi-paragraph reply in that response. The model's context, meanwhile, retains the reply — it will later refer back to a message the user never received and that no longer exists anywhere.

This is a stronger variant of #74184 and #77358: those report the reply being hidden by the renderer while still recoverable. In the cases below the text never persists, so no transcript inspection, Stop hook, or resume can recover it. Data loss, not display loss.

A second, related defect compounds it: the harness may end the turn at the ScheduleWakeup tool result (whose result text says "Nothing more to do this turn") even when the model intended to continue and stream more output afterward. The combination means there is no safe ordering of "write the reply" and "call ScheduleWakeup" within one turn:

  • Text and tool call in one response → the text is destroyed (this issue).
  • Tool call first, text intended after → the turn ends at the tool result; the text never happens.

Environment

  • Claude Code 2.1.220, macOS 26.5.2 (native terminal, interactive session)
  • Model: claude-fable-5
  • Observed with ScheduleWakeup armed by a UserPromptSubmit-hook nudge (an auto-handoff automation), and previously with /loop-style pacing

Evidence (transcript-level)

Session d03dfebb-ea7e-4bb1-8369-6f7ff8457370, 2026-07-30 ~08:49 CT. The model streamed a ~1,800-char reply and a ScheduleWakeup call in the same response. The jsonl's assistant entry for that response (line 137) contains only the tool_use block:

line 137 [('tool_use', 'ScheduleWakeup')]
  ctx line 134 user      ['tool_result']
  ctx line 135 assistant ['thinking']
  ctx line 136 assistant ['thinking']
  ctx line 137 assistant ['tool_use']      <- lone ScheduleWakeup, no text block
  ctx line 138 user      ['tool_result']

The ~1,800-char reply appears nowhere in the file. Same shape verified twice in session 349dd7ec (2026-07-29) and again in ebc4a9ba (2026-07-30 ~08:43). The user saw nothing; the model's next turn referred back to "the reply above."

Triage signature (may help)

We log every Stop-hook payload. On destroyed turns, the Stop payload's last_assistant_message field is absent, while every healthy turn carries it — across hundreds of logged stops the absence + last tool ScheduleWakeup correlates 1:1 with destroyed replies. We now block on exactly that signature in a Stop hook as a client-side mitigation.

Repro

  1. In an interactive session, get the model to write a substantive text reply and call ScheduleWakeup in the same response (e.g. an instruction like "answer, then as the very last action of the turn schedule a wakeup").
  2. Turn ends at the tool call; the reply is not rendered.
  3. Inspect the session jsonl: the assistant entry holds only the tool_use block — the streamed text is not in the file.

Expected

  • Text blocks streamed in the same response as a ScheduleWakeup tool_use persist to the transcript and render.
  • A pending ScheduleWakeup result does not force-end the turn if the model continues the response.

Impact

Any workflow that arms ScheduleWakeup on a user-facing turn (/loop dynamic pacing, keep-warm pings, custom auto-handoff automations) silently destroys the turn's reply. We burned three fix rounds on hook-side orderings before proving no ordering is safe and moving the scheduling out of the session entirely.

View original on GitHub ↗