Skip to content

fix: persist pending followup queues across gateway restart#1178

Open
BingqingLyu wants to merge 2 commits into
mainfrom
fork-pr-51623-fix-persist-pending-messages-across-restart
Open

fix: persist pending followup queues across gateway restart#1178
BingqingLyu wants to merge 2 commits into
mainfrom
fork-pr-51623-fix-persist-pending-messages-across-restart

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes openclaw#51620 — gateway restart drops queued and in-flight inbound messages.

Changes

New: src/auto-reply/reply/queue/persist.ts

Serializes all non-empty FOLLOWUP_QUEUES entries to state/pending-messages.json before shutdown. Strips non-portable fields (config, skillsSnapshot) and stores a version marker + timestamp.

New: src/gateway/server-pending-messages.ts

On startup, reads the persisted file, injects missed messages as system events into their respective sessions (so the agent processes them on next turn), then deletes the file. Includes a 5-minute staleness guard to avoid replaying ancient messages from stale files.

Modified: src/gateway/server-close.ts

Calls persistFollowupQueues(FOLLOWUP_QUEUES) before chatRunState.clear() so queue state is captured while still available. Best-effort — failure doesn't block shutdown.

Modified: src/gateway/server-startup.ts

Calls replayPersistedPendingMessages() 1.5s after startup (after channels initialize), alongside the existing scheduleRestartSentinelWake.

Tests

16 new tests covering:

  • persistFollowupQueues: empty queues, non-empty queues, config stripping, multiple queues
  • consumePersistedQueues: no file, read+delete, staleness guard (>5min), corrupt file, wrong version
  • Round-trip: persist → consume
  • replayPersistedPendingMessages: no entries, empty entries, system event injection, multiple queues, prompt truncation, empty items skip

Limitations / Follow-up

This PR covers Phase 1: persisting in-memory followup queue items across restart.

Still not covered (follow-up work):

  • Phase 2: Messages arriving during the drain window (hitting GatewayDrainingError) — these are rejected at the channel level before reaching the queue
  • Phase 3: Channel-level message acknowledgment deferral — for channels that support it (Slack Socket Mode), defer ACK until message is processed or persisted so the platform can redeliver

Testing

npx vitest run src/auto-reply/reply/queue/persist.test.ts src/gateway/server-pending-messages.test.ts

All 16 tests pass.


Upstream issue: openclaw#51620

Before this change, in-memory followup queue items (FollowupQueueState)
were silently lost when the gateway restarted. Messages arriving during
the drain window hit GatewayDrainingError and were dropped with no
persistence or replay mechanism.

This adds:

1. Queue persistence (persist.ts): Before shutdown, serialize non-empty
   followup queues to state/pending-messages.json. Strips non-portable
   fields (config, skillsSnapshot) and includes a version marker.

2. Post-restart replay (server-pending-messages.ts): On startup, read
   the persisted file, inject the missed messages as system events into
   their respective sessions, and delete the file. Includes a 5-minute
   staleness guard to avoid replaying ancient messages.

3. Close handler integration: persistFollowupQueues() is called before
   chatRunState.clear() in server-close.ts so queue state is captured
   while still available.

4. Startup integration: replayPersistedPendingMessages() runs 1.5s after
   startup (after channels initialize) alongside the existing restart
   sentinel wake.

Tests: 16 new tests covering persist, consume, round-trip, staleness,
corruption handling, and system event injection.

Closes openclaw#51620
- test/restart-persistence.test.ts: 3 integration tests that exercise
  the full persist → consume → replay pipeline with realistic multi-session
  multi-channel scenarios, staleness guard, and corruption handling

- test/prove-restart-persistence.sh: standalone bash/node script that
  proves the mechanism end-to-end against the compiled build, with
  human-readable step-by-step output
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gateway restart drops queued and in-flight inbound messages

2 participants