ScheduleWakeup reports a scheduled time outside /loop, then never fires
Bug: ScheduleWakeup reports success outside /loop, then never fires
Component: Claude Code — ScheduleWakeup tool
Severity: Medium–High for unattended/long-running work
Reproducibility: Consistent (observed repeatedly)
Summary
ScheduleWakeup accepts a call made outside an active /loop session, returns a success message
naming a concrete wake time, and then never fires. The success string also tells the model to stop
working for the turn, so the model ends its turn waiting on a wakeup that structurally cannot occur.
Repro
- Start a normal Claude Code session (no
/loop). - Have the model call
ScheduleWakeupwith a plainprompt(not a/loopinput, not the
<<autonomous-loop-dynamic>> sentinel), e.g. delaySeconds: 1200.
- Observe the tool result.
- Wait past the stated time.
Expected
One of:
- the call is refused with a clear reason ("no active
/loop;ScheduleWakeuponly resumes a
dynamic loop"), or
- it works as a general deferred re-invocation.
Either is fine. What matters is that the return value reflects reality.
Actual
The tool returns, verbatim:
Next wakeup scheduled for 10:31:00 (in 1221s). Nothing more to do this turn — the harness
re-invokes you when the wakeup fires or a task-notification arrives.
No wakeup occurs. The session sits idle until the user speaks. Confirmed by the transcript: there is no
model turn between the ScheduleWakeup call and the user's next message, so no re-invocation happened.
Why this is worse than a plain no-op
The failure is actively misleading in three ways:
- It states a specific time.
10:31:00reads as a scheduling confirmation, not a best effort. - It instructs the model to stand down — "Nothing more to do this turn". A correct-behaving model
should stop after a successful schedule. So the message converts a silent no-op into a deliberate,
justified stall.
- There is no observable signal of failure. Nothing is logged, no later error surfaces. The only way
to detect it is to notice that time passed and nothing happened — which requires a human.
Impact
In an unattended or long-running session this is a silent indefinite stall. Concrete instance:
- A background workflow was running and produced no journal file, so a completion notification was the
only passive signal — and that notification had already failed to arrive once (a prior run of the same
workflow hung for ~8 hours without notifying).
- Precisely because passive signals had proven unreliable, the model scheduled an active check.
- The wakeup never fired, so the workflow went unmonitored for 23 minutes until the user intervened.
The pattern is self-defeating: the tool is most attractive exactly when passive notification is untrusted,
which is exactly when its silent failure is most costly.
Suggested fixes (in preference order)
- Refuse the call outside an active dynamic
/loop, with a reason. Cheapest, and it makes the
documented constraint enforceable rather than advisory.
- If refusal is undesirable, change the return string so it cannot be read as a confirmation —
e.g. No active /loop; nothing scheduled. Do not end your turn expecting a wakeup. The current
wording is the harmful part.
- Longer term, consider a general deferred re-invocation primitive. The underlying need — "check
back on background work in N minutes" — is real and currently has no reliable mechanism when a task
emits no notification.
Notes
- The tool's own description does scope it to
/loopdynamic mode, so a model calling it outside that
context is misusing it. The bug is not that it declines to work — it is that it claims to have
worked. A tool documented as loop-only should not return a scheduled timestamp when no loop exists.
- Related gap that made this matter: a background
Workflowthat hangs produces no completion
notification and no result file, and is therefore indistinguishable from one still running. The
only reliable liveness signal found was working-tree file mtimes.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗