Skip to content

[Bug]: Telegram context-overflow retry replays same inbound message and delivers stale turn #76424

@coolmanns

Description

@coolmanns

Bug type

Behavior bug / stale turn replay after context-overflow auto-compaction in Telegram channel session.

Summary

A Telegram direct agent session hit context overflow during an active turn. OpenClaw auto-compacted successfully and logged retrying prompt, but the same inbound Telegram message was appended into the compacted transcript a second time with the same external message_id. After that, later user messages arrived while the stale/retried turn continued, and the assistant delivered replies for older exchanges after the user had moved on.

This looks related to the existing compaction/replay/queued-turn family, but the observed failure is specific:

  • channel-backed session
  • context-overflow during tool-heavy active turn
  • auto-compaction succeeds
  • retry uses the original inbound/current-message metadata again
  • compacted transcript receives a duplicate user turn for the same external message id
  • stale assistant output is delivered after newer Telegram messages

Environment

  • OpenClaw: 2026.5.2 (8b2a6e5)
  • Install: npm global
  • OS: Linux x64
  • Runtime: Gateway service via systemd user unit
  • Channel: Telegram direct session
  • Provider/model: openai-codex/gpt-5.5
  • Queue mode observed in status: steer

Evidence

Transcript excerpt from a Telegram direct session, redacted to avoid exposing private chat identifiers:

20:40:25.113 user       <plugin warning question>
20:40:25.117 runtime    message_id="51024"
20:40:40.022 assistant  starts handling that question
20:40:46.498 gateway    context-overflow-diag ... error=Context overflow: estimated context size exceeds safe threshold during tool loop
20:40:46.498 gateway    context overflow detected ... attempting auto-compaction
20:41:08.065 gateway    auto-compaction succeeded ... retrying prompt
20:41:08.644 user       <same plugin warning question again>
20:41:08.647 runtime    message_id="51024"   <-- same external Telegram message id appended again
20:42:59.643 user       <newer message, message_id="51027">
20:58:38.744 user       <newer message, message_id="51029">
20:59:57.936 user       "You are still responding to old message exchanges", message_id="51031"
21:00:04.214 assistant  acknowledges stale old-turn behavior

The duplicate was not a new Telegram inbound from the user: the transcript contains the same external Telegram message_id (51024) twice, before and after compaction retry.

Gateway log around the duplicate:

[agent/embedded] [context-overflow-diag] sessionKey=<telegram direct session> provider=openai-codex/gpt-5.5 source=assistantError messages=251 sessionFile=<session jsonl> diagId=ovf-... compactionAttempts=0 observedTokens=unknown error=Context overflow: estimated context size exceeds safe threshold during tool loop.
[agent/embedded] context overflow detected (attempt 1/3); attempting auto-compaction for openai-codex/gpt-5.5
[agent/embedded] auto-compaction succeeded for openai-codex/gpt-5.5; retrying prompt

Expected behavior

After auto-compaction succeeds, retry should continue the in-flight turn without appending the same channel inbound message/current message metadata as a fresh user turn. Channel-backed external message ids should be idempotent across compaction retries.

If a retry cannot safely preserve in-flight state, the runtime should fail/suppress/manual-recover rather than deliver stale output for an older external message after newer Telegram messages have arrived.

Actual behavior

The compacted transcript re-appended the same user content and same Telegram message_id, then older-turn assistant output continued after newer user messages were already present.

Suspected root cause

In the embedded PI retry loop, after context-overflow auto-compaction:

  • adoptCompactionTranscript() updates activeSessionId / activeSessionFile
  • the loop logs auto-compaction succeeded ... retrying prompt
  • the next runEmbeddedAttemptWithBackend(...) still receives the original:
    • prompt: params.prompt
    • transcriptPrompt: params.transcriptPrompt
    • currentMessageId: params.currentMessageId
    • sessionFile: activeSessionFile

The retry therefore appears to submit the same inbound prompt/current-message context into the new compacted transcript as though it were a fresh channel message.

A likely fix is one of:

  1. Make overflow/compaction retry idempotent for channel-backed prompts by detecting an already-recorded external currentMessageId in the active transcript and switching the retry to a continuation prompt instead of re-submitting the original inbound prompt.
  2. Add a per-session/per-message generation fence so stale channel replies from superseded runs are suppressed if newer external message ids have arrived.
  3. Treat channel-backed compaction retry like restart recovery: fail/manual-recover instead of hidden replay when external delivery state cannot be guaranteed.

Related existing work

This appears related but not identical to:

Minimal diagnostic assertion that would catch this

For channel-backed sessions, after compaction/retry, the same external currentMessageId should not produce two role=user transcript entries unless the second inbound was actually received again from the channel transport.

Regression test idea:

  1. Create a channel-backed session with currentMessageId = X.
  2. Force context-overflow auto-compaction during the active turn.
  3. Retry after compaction.
  4. Assert transcript contains only one user/runtime-context pair for external message id X.
  5. Inject a newer message id Y while old run is still active/finishing.
  6. Assert any final output from the old run is suppressed or explicitly fenced if it would be delivered after Y.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions