#19518: fix: await block reply flush before tool execution
agents
size: XS
Cluster:
Tool Result Handling Improvements
## Summary
- **Problem:** Block reply flush (`onBlockReplyFlush`) was called with `void` (fire-and-forget) in `handleToolExecutionStart`, causing narration text to arrive **after** message tool sends due to a race condition between the flush HTTP delivery and the tool's own HTTP call.
- **Why it matters:** Users see messages out of order — tool-sent messages appear before the narration text that should precede them. Affects all channels with `blockStreaming` enabled.
- **What changed:** Changed `void ctx.params.onBlockReplyFlush()` to `await ctx.params.onBlockReplyFlush()` in `handleToolExecutionStart`, so the pipeline flush completes before the handler returns. Updated comment in the event dispatcher to document the ordering guarantee.
- **What did NOT change:** The SDK subscribe callback signature (`void` return type) is unchanged. The `await` operates within the async handler — the SDK still fire-and-forgets the handler, but the flush now completes before any subsequent handler logic.
## Change Type
- [x] Bug fix
## Scope
- [x] Gateway / orchestration
## Linked Issue
- Closes #15968
- Related #11044, #13944
## User-visible / Behavior Changes
Block reply narration text (e.g., "Let me check...") now arrives before message tool sends in the same turn, when `blockStreaming` is enabled. Previously, the ordering was non-deterministic.
## Security Impact
- New permissions/capabilities? No
- Secrets/tokens handling changed? No
- New/changed network calls? No
- Command/tool execution surface changed? No
- Data access scope changed? No
## Repro + Verification
### Environment
- OS: Linux (tested), originally reported on macOS
- Runtime: Node 22
- Model/provider: Any
- Integration/channel: BlueBubbles, Telegram, any channel with blockStreaming
### Steps
1. Enable `blockStreaming: true` on a channel
2. Send a prompt that causes the agent to write narration text, then call the `message` tool
3. Observe message ordering in the channel
### Expected
Narration text arrives before message tool send.
### Actual (before fix)
Message tool send arrives first, narration arrives second (race condition).
## Evidence
- [x] Failing test before + passing after: `handleToolExecutionStart block reply flush ordering (#15968)` test verifies flush completes before handler returns. Test fails with `void` (fire-and-forget), passes with `await`.
## Human Verification
- Verified: Test confirms ordering invariant (flush_complete before handler_returned)
- Edge cases: Handler rejection is still caught by `.catch()` in the event dispatcher
- Not verified: Live channel testing (no BlueBubbles setup available)
## Compatibility / Migration
- Backward compatible? Yes
- Config/env changes? No
- Migration needed? No
## Failure Recovery
- Revert this single commit
- Watch for: slower tool execution start (flush adds latency before handler returns, but this is intentional — flush was always meant to complete before tool execution)
## Risks and Mitigations
- Risk: Flush timeout could delay tool execution start notification
- Mitigation: `onBlockReplyFlush` already has its own timeout via the block reply pipeline; the handler `.catch()` prevents unhandled rejections
## AI Disclosure
- AI-assisted: Yes (OpenClaw agent)
- Testing level: Unit test verifying ordering invariant
- Understanding: Confirmed — traced full event flow from SDK agent-loop through subscribe handlers to pipeline delivery
### Limitation Note
This fix reduces but does not fully eliminate the race condition. The SDK's `subscribe` callback is fire-and-forget (`(e: AgentEvent) => void`), so the agent loop proceeds to `tool.execute()` without waiting for the handler. The `await` ensures the flush HTTP call completes within the handler, but tool execution may still start concurrently via the SDK. A complete fix would require either SDK-level pre-tool hooks or serializing all outbound messages (block replies + message tool sends) through a single delivery queue.
Most Similar PRs
#15996: fix(agents): messages arrive out of order — tool output beats narra...
by yinghaosang · 2026-02-14
79.4%
#22886: fix: await onBlockReplyFlush before tool execution to preserve mess...
by botverse · 2026-02-21
79.3%
#15920: fix: await coalescer flush before tool execution
by Bridgerz · 2026-02-14
78.9%
#22994: fix(gateway): flush text buffer before tool events to prevent data ...
by youngkent · 2026-02-21
75.8%
#9171: Fix: Route tool result deliveries through BlockReplyPipeline for pr...
by vishaltandale00 · 2026-02-04
74.8%
#16275: fix(agents): prevent rapid-fire duplicate messages during tool exec...
by heyhudson · 2026-02-14
71.9%
#19141: fix(telegram): serialize per-chat sends and await block reply flush...
by botverse · 2026-02-17
71.5%
#20052: fix(agents): suppress narration text when assistant message has too...
by lailoo · 2026-02-18
71.4%
#23164: fix: suppress narration text when assistant message contains tool c...
by ahdernasr · 2026-02-22
69.7%
#7760: fix(agents): resolve message ordering conflict during tool execution
by aryan877 · 2026-02-03
67.7%