Bug
When a backgrounded exec session exits, notifyOnExit calls requestHeartbeatNow({ reason: \exec:${session.id}:exit` }). However, the heartbeat execution path skips turns when HEARTBEAT.mdis effectively empty, and only bypasses that guard for specific reason strings:exec-event, wake, hook:, cron:`.
The reason exec:<id>:exit doesn't match any of these, so the heartbeat is skipped entirely. The system event remains queued until the next user message.
Expected behavior
Background exec completion should trigger an agent turn even when HEARTBEAT.md is empty, since the system event contains actionable information the agent needs to process.
Observed behavior
- Agent dispatches background exec (coding agent), exits after ~18 minutes
notifyOnExit enqueues system event and requests heartbeat with reason exec:<session-id>:exit
- Heartbeat handler sees empty HEARTBEAT.md, reason doesn't match bypass list → skips
- System event sits in queue for 70+ minutes until user sends next message
- Event is then bundled into the user's message turn
Suggested fix
Change notifyOnExit to use exec-event as the wake reason, which already has dedicated heartbeat behavior:
requestHeartbeatNow({ reason: "exec-event", sessionKey });
Or extend the bypass check to recognize exec: prefixed reasons:
const isExecEventReason = opts.reason === "exec-event" || Boolean(opts.reason?.startsWith("exec:"));
Secondary issue
Even when a heartbeat does fire with pending system events, if the model responds HEARTBEAT_OK, the drained system events are silently consumed with no user-visible output. Consider re-enqueueing drained events when the heartbeat normalizes to ack-only.
Environment
- OpenClaw 2026.2.17
- Empty HEARTBEAT.md (comments only)
Bug
When a backgrounded exec session exits,
notifyOnExitcallsrequestHeartbeatNow({ reason: \exec:${session.id}:exit` }). However, the heartbeat execution path skips turns whenHEARTBEAT.mdis effectively empty, and only bypasses that guard for specific reason strings:exec-event,wake,hook:,cron:`.The reason
exec:<id>:exitdoesn't match any of these, so the heartbeat is skipped entirely. The system event remains queued until the next user message.Expected behavior
Background exec completion should trigger an agent turn even when HEARTBEAT.md is empty, since the system event contains actionable information the agent needs to process.
Observed behavior
notifyOnExitenqueues system event and requests heartbeat with reasonexec:<session-id>:exitSuggested fix
Change
notifyOnExitto useexec-eventas the wake reason, which already has dedicated heartbeat behavior:Or extend the bypass check to recognize
exec:prefixed reasons:Secondary issue
Even when a heartbeat does fire with pending system events, if the model responds
HEARTBEAT_OK, the drained system events are silently consumed with no user-visible output. Consider re-enqueueing drained events when the heartbeat normalizes to ack-only.Environment