Summary
Restart-sentinel continuation delivery can become stuck in ~/.openclaw/session-delivery-queue/ after a transient Telegram sendMessage network failure. The continuation entry is persisted and retry metadata is updated, but there appears to be no periodic/session wake recovery after the immediate startup drain fails, so the user never receives the post-restart continuation/report until another gateway startup happens.
This is related to Telegram transport flakiness / delivery durability, but distinct from ordinary outbound message queue recovery: restart continuation uses the session-delivery queue in server-restart-sentinel, and a failed immediate drain remains pending without an automatic follow-up retry.
Environment
- OpenClaw:
2026.4.29 (a448042)
- Host: macOS 26.4.1 arm64, Node 25.9.0
- Channel: Telegram long-poll bot mode
- Surface: restart-sentinel continuation for
agent:system-architect:telegram:direct:<user>
Observed evidence
Gateway log:
2026-05-02T21:16:03.924+08:00 [restart-sentinel] restart continuation: retry failed for entry 5bfb077fa406f9dad97b2a82488f95bef182e9ae4f9672620de895622f578052: Network request for 'sendMessage' failed!
The entry remained pending afterwards:
{
"kind": "agentTurn",
"sessionKey": "agent:system-architect:telegram:direct:<user>",
"message": "Post-restart continuation ...",
"route": {
"channel": "telegram",
"to": "telegram:<user>",
"accountId": "system-architect",
"chatType": "direct"
},
"id": "5bfb077fa406f9dad97b2a82488f95bef182e9ae4f9672620de895622f578052",
"enqueuedAt": 1777727345013,
"retryCount": 1,
"lastAttemptAt": 1777727763925,
"lastError": "Network request for 'sendMessage' failed!"
}
The entry was still present at:
~/.openclaw/session-delivery-queue/5bfb077fa406f9dad97b2a82488f95bef182e9ae4f9672620de895622f578052.json
During the same day the Telegram channel had intermittent transport failures, but later openclaw status --deep reported Telegram OK, so this is a transient-send/recovery problem rather than a permanent token/config failure.
Examples from the same day:
2026-05-02T13:44:10+08:00 [telegram] ... Network request for 'getUpdates' failed!
2026-05-02T16:02:34+08:00 [telegram] Polling stall detected ... Network request for 'getUpdates' failed!
2026-05-02T18:12:28+08:00 [telegram] Polling stall detected ... Network request for 'getUpdates' failed!
2026-05-02T21:16:03+08:00 [restart-sentinel] restart continuation: retry failed ... Network request for 'sendMessage' failed!
Source-code trace
Installed dist paths on 2026.4.29:
dist/server-restart-sentinel-_OFhLVvA.js
dist/hook-client-ip-config-BCNYpeHn.js
Relevant flow:
scheduleRestartSentinelWake() loads the restart sentinel at gateway startup.
loadRestartSentinelStartupTask() enqueues a restart continuation via enqueueSessionDelivery(buildQueuedRestartContinuation(...)).
- It then calls
drainRestartContinuationQueue({ entryId }).
drainRestartContinuationQueue() calls drainPendingSessionDeliveries(... selectEntry: entry.id === entryId, bypassBackoff: true).
- If
deliverQueuedSessionDelivery() throws (Telegram sendMessage network failure), drainQueuedEntry() calls failSessionDelivery(), increments retryCount, stores lastError, and leaves the JSON in session-delivery-queue/.
recoverPendingRestartContinuationDeliveries() exists, but startup wiring in activateGatewayScheduledServices() only calls recoverPendingSessionDeliveries() once after startup (setTimeout(..., 1250)), not periodically.
So if the immediate startup continuation drain fails after that startup recovery window, the entry remains pending until the next gateway startup (or manual intervention).
Expected behavior
Restart continuation delivery should be durable and eventually delivered after transient Telegram/network failures, without requiring another gateway restart.
At minimum:
- Pending restart continuation entries should be retried after their backoff expires.
- Retries should continue up to the existing max retry limit (
retryCount >= 5).
- Permanent delivery errors (e.g. 400 chat not found / thread not found) should be moved to failed with clear diagnostics.
- Transient transport errors should not leave the restart continuation invisible to the user.
Actual behavior
A transient Telegram sendMessage network failure leaves the restart continuation pending in session-delivery-queue/ with retryCount: 1, but no automatic retry occurs during normal gateway operation. The user sees no post-restart continuation/report.
Impact
This weakens the safety of the new gateway restart runner. The runner correctly writes a sentinel and enqueues continuation, but a single transient Telegram send failure can make the continuation appear lost. That pushes operators back toward manual checking and makes protected restart workflows less reliable.
Suggested fix
Add periodic/backoff-based recovery for session-delivery queue entries, similar in spirit to outbound delivery recovery.
Possible implementation options:
- Start a lightweight scheduled recovery loop for
recoverPendingRestartContinuationDeliveries() after gateway startup, e.g. every 30-60s, respecting isSessionDeliveryEligibleForRetry() and max retries.
- Or, after
drainRestartContinuationQueue() fails an entry, schedule a one-shot retry timer for that entry's next backoff deadline.
- Add status visibility for pending/failed session-delivery queue entries in
openclaw status --deep or a diagnostic command.
- Consider aligning restart continuation queue behavior with the normal outbound
delivery-queue recovery behavior, while preserving idempotency via the existing idempotencyKey.
Related
Possibly related but not identical:
This issue is specifically about restart-sentinel session-delivery retry scheduling after a transient sendMessage failure.
Summary
Restart-sentinel continuation delivery can become stuck in
~/.openclaw/session-delivery-queue/after a transient TelegramsendMessagenetwork failure. The continuation entry is persisted and retry metadata is updated, but there appears to be no periodic/session wake recovery after the immediate startup drain fails, so the user never receives the post-restart continuation/report until another gateway startup happens.This is related to Telegram transport flakiness / delivery durability, but distinct from ordinary outbound message queue recovery: restart continuation uses the session-delivery queue in
server-restart-sentinel, and a failed immediate drain remains pending without an automatic follow-up retry.Environment
2026.4.29 (a448042)agent:system-architect:telegram:direct:<user>Observed evidence
Gateway log:
The entry remained pending afterwards:
{ "kind": "agentTurn", "sessionKey": "agent:system-architect:telegram:direct:<user>", "message": "Post-restart continuation ...", "route": { "channel": "telegram", "to": "telegram:<user>", "accountId": "system-architect", "chatType": "direct" }, "id": "5bfb077fa406f9dad97b2a82488f95bef182e9ae4f9672620de895622f578052", "enqueuedAt": 1777727345013, "retryCount": 1, "lastAttemptAt": 1777727763925, "lastError": "Network request for 'sendMessage' failed!" }The entry was still present at:
During the same day the Telegram channel had intermittent transport failures, but later
openclaw status --deepreported Telegram OK, so this is a transient-send/recovery problem rather than a permanent token/config failure.Examples from the same day:
Source-code trace
Installed dist paths on 2026.4.29:
dist/server-restart-sentinel-_OFhLVvA.jsdist/hook-client-ip-config-BCNYpeHn.jsRelevant flow:
scheduleRestartSentinelWake()loads the restart sentinel at gateway startup.loadRestartSentinelStartupTask()enqueues a restart continuation viaenqueueSessionDelivery(buildQueuedRestartContinuation(...)).drainRestartContinuationQueue({ entryId }).drainRestartContinuationQueue()callsdrainPendingSessionDeliveries(... selectEntry: entry.id === entryId, bypassBackoff: true).deliverQueuedSessionDelivery()throws (TelegramsendMessagenetwork failure),drainQueuedEntry()callsfailSessionDelivery(), incrementsretryCount, storeslastError, and leaves the JSON insession-delivery-queue/.recoverPendingRestartContinuationDeliveries()exists, but startup wiring inactivateGatewayScheduledServices()only callsrecoverPendingSessionDeliveries()once after startup (setTimeout(..., 1250)), not periodically.So if the immediate startup continuation drain fails after that startup recovery window, the entry remains pending until the next gateway startup (or manual intervention).
Expected behavior
Restart continuation delivery should be durable and eventually delivered after transient Telegram/network failures, without requiring another gateway restart.
At minimum:
retryCount >= 5).Actual behavior
A transient Telegram
sendMessagenetwork failure leaves the restart continuation pending insession-delivery-queue/withretryCount: 1, but no automatic retry occurs during normal gateway operation. The user sees no post-restart continuation/report.Impact
This weakens the safety of the new gateway restart runner. The runner correctly writes a sentinel and enqueues continuation, but a single transient Telegram send failure can make the continuation appear lost. That pushes operators back toward manual checking and makes protected restart workflows less reliable.
Suggested fix
Add periodic/backoff-based recovery for session-delivery queue entries, similar in spirit to outbound delivery recovery.
Possible implementation options:
recoverPendingRestartContinuationDeliveries()after gateway startup, e.g. every 30-60s, respectingisSessionDeliveryEligibleForRetry()and max retries.drainRestartContinuationQueue()fails an entry, schedule a one-shot retry timer for that entry's next backoff deadline.openclaw status --deepor a diagnostic command.delivery-queuerecovery behavior, while preserving idempotency via the existingidempotencyKey.Related
Possibly related but not identical:
This issue is specifically about restart-sentinel session-delivery retry scheduling after a transient
sendMessagefailure.