Skip to content

Agent's final agent_message stranded when LLM forgets to call configured delivery tool (no fallback delivery from task_complete) #85714

Description

@richardmqq

Summary

When an OpenClaw agent that delivers via tools.message (e.g. the bundled system-architect agent) occasionally forgets to invoke the message tool on its final turn and instead emits the response as a plain agent_message (not the conventional "NO_REPLY" sentinel), the response is silently stranded in the Codex rollout JSONL and never reaches the originating channel. From the user's perspective the bot looks unresponsive even though task_complete fired normally; from the gateway's perspective there is no error to surface, just no outbound.

There is no fallback that takes the task_complete.last_agent_message and routes it to the inbound channel when the model has failed to call the configured delivery tool.

Environment

  • OpenClaw: 2026.5.20 (/opt/homebrew/lib/node_modules/openclaw)
  • Codex app-server: 0.130.0 (/opt/homebrew/lib/node_modules/@openai/codex)
  • Agent: system-architect (codex-app-server backend, model openai/gpt-5.5)
  • Telegram inbound to @RMHomeSystemArchitect_bot, direct chat
  • OS: macOS 15 (Darwin 25.4)

Repro (observed in production, 2026-05-23 20:21-20:22 local)

This agent's system prompt establishes a strict delivery contract:

  1. Reply to the user by calling tools.message({action:"send", message:"..."}) exactly once.
  2. End the turn by emitting agent_message: "NO_REPLY" as a sentinel so the orchestrator knows the work is done and does not re-deliver.

The contract held for the prior 7 turns of the same session. Sequence of those turns (per the codex rollout JSONL ~/.openclaw/agents/system-architect/agent/codex-home/sessions/<date>/rollout-*.jsonl):

turn start (local) tools.message execs agent_message count final agent_message content
2026-05-23 20:14:51 1 2 "NO_REPLY"
2026-05-23 20:17:49 1 2 "NO_REPLY"
2026-05-23 20:18:09 1 1 "NO_REPLY"
2026-05-23 20:19:19 1 4 "NO_REPLY"
2026-05-23 20:19:21 1 2 "NO_REPLY"
2026-05-23 20:20:30 1 1 "NO_REPLY"
2026-05-23 20:21:35 0 1 the actual 3,738-char response, no NO_REPLY sentinel

The final turn fired task_complete (turn_id 019e54c8-ad57-7d50-bb70-e9148e09f514) with last_agent_message set to the entire 3,738-char reply. The model simply omitted the tools.message call this once.

Gateway-side log over the same window (/tmp/openclaw/openclaw-<date>.log) shows only:

  • Inbound message telegram:6689123501 -> @RMHomeSystemArchitect_bot (direct, 42 chars) at 20:21:35.705
  • using external OAuth credential after refresh failure at 20:21:37.596

No outbound, no stall warning, no recovery — just nothing. User waited 7+ minutes thinking the bot was stuck.

Why this matters

  • This is a silent message-loss path: the user is misled into thinking the bot is hung when in fact the response was generated and stored on disk.
  • LLMs (including gpt-5.5) demonstrably forget multi-step delivery contracts after long sessions (token-cached prefix → drift). The 7-turn-of-7 success rate before this turn shows the model does know the rule, it just occasionally lapses.
  • The recovery is impossible for the user without root access to the agent's codex rollout files — even an operator who knows about the bug has to manually extract the stranded agent_message and re-send via openclaw message send.

Suggested fix shape

In the codex-app-server bridge / agent run handler, on task_complete:

  1. Look at the last_agent_message (already exposed in the task_complete event payload).
  2. If it is non-empty AND not equal to the agent's configured sentinel (e.g. "NO_REPLY") AND no tools.message/equivalent outbound has been delivered during this turn → auto-deliver last_agent_message to the originating channel as a fallback.
  3. Log a WARN like agent failed to invoke configured delivery tool; auto-delivering last_agent_message as fallback so operators can see this happened.

This makes the failure mode self-healing instead of silently message-lost.

If the sentinel is agent-specific (it is, for system-architect), the agent config could expose agents.list[].deliveryContract: { sentinel: "NO_REPLY" } so the bridge knows what to compare against. Default to "any non-empty final agent_message that wasn't already sent via tools.message".

Workaround (current)

Operator manually pulls the stranded agent_message from the codex rollout JSONL:

python3 -c "
import json
for line in open('~/.openclaw/agents/<agent-id>/agent/codex-home/sessions/<date>/rollout-*.jsonl'):
    d = json.loads(line)
    p = d.get('payload',{})
    if p.get('type')=='agent_message' and p.get('message','') != 'NO_REPLY':
        print(p['message'])
" > /tmp/stranded.txt

openclaw message send \
  --channel telegram --account <agent> --target <chat-id> \
  --message "$(cat /tmp/stranded.txt)" --json

Not a path a normal user can be expected to discover.

Related

  • Not duplicates: #85251 (Codex turn/started silent hang — different layer: agent doesn't even start a turn there), #83950 (telegram polling stall — inbound side), #85224 (gateway respawn — supervisor layer).
  • All four are different "silent message loss" classes on this Mac. This one is the most subtle because everything looks normal except the user.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions