openclaw - ✅(Solved) Fix Subagent completion announce retries after Slack completion was already delivered [1 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#84270Fetched 2026-05-20 03:42:00
View on GitHub
Comments
2
Participants
3
Timeline
12
Reactions
1
Author
Timeline (top)
labeled ×5commented ×2mentioned ×2subscribed ×2

For expectsCompletionMessage=true subagent runs, OpenClaw can mark completion delivery as failed even when the child already delivered and verified the Slack completion reply. The later requester-session announce can correctly return NO_REPLY to avoid duplicate user-visible output, but the subagent registry treats that as an undelivered completion and retries until retry-limit.

Root Cause

It made no message(send) call because the Slack reply was already present. Despite that, the registry retried the completion announce three times and logged:

Fix Action

Fixed

PR fix notes

PR #84383: fix(agents): credit delivered subagent completions

Description (problem / solution / changelog)

Summary

  • Problem: expectsCompletionMessage subagent cleanup treated the later parent announce as the only delivery signal, so a parent NO_REPLY could retry even after the requester thread already had a committed delivery mirror.
  • Solution: when a completion announce reports no visible delivery, lifecycle cleanup now checks the requester session for a matching openclaw/delivery-mirror assistant message before retrying.
  • What changed: matching prior delivery mirrors clear the delivery error path, preserve the mirror timestamp as completionDeliveredAt, finalize cleanup as delivered, and skip pending-final retry bookkeeping.
  • What did NOT change (scope boundary): no channel delivery APIs, prompt instructions, message-tool requirements, config defaults, or provider behavior changed.

Motivation

  • Fixes the bookkeeping failure reported in #84270, where Slack already had the subagent completion reply but the registry retried the completion announce until retry-limit.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #84270
  • This PR fixes a bug or regression

Real behavior proof (required for external PRs)

  • Behavior addressed: completion cleanup now credits an already-committed requester delivery mirror instead of retrying a parent NO_REPLY announce.
  • Real environment tested: local macOS source checkout, Node/Vitest test harness for the subagent lifecycle controller.
  • Exact steps or command run after this patch: node scripts/run-vitest.mjs src/agents/subagent-registry-lifecycle.test.ts; pnpm check:changed; git diff --check.
  • Evidence after fix: copied terminal output from the full lifecycle file run: Test Files 2 passed (2) and Tests 46 passed (46).
  • Observed result after fix: the regression asserts chat.history is queried for the requester session, completionDeliveredAt/completionAnnouncedAt are set from the mirror timestamp, lastAnnounceDeliveryError is cleared, pendingFinalDelivery and announceRetryCount remain unset, detached task delivery is marked delivered, and logAnnounceGiveUp is not called.
  • What was not tested: live Slack workspace delivery with a real gateway session.
  • Before evidence: the issue log showed Subagent announce give up (retry-limit) ... retries=3; the pre-existing focused lifecycle proof covered the old failure bookkeeping path with completion agent did not produce a visible reply / UNAVAILABLE: requester wake failed style delivery errors.

Root Cause (if applicable)

  • Root cause: cleanup finalized based on didAnnounce = delivery.delivered from the later parent-mediated announce only, and did not consult requester-session delivery mirror evidence from the child’s already-committed visible reply.
  • Missing detection / guardrail: lifecycle tests covered invisible completion announce failure and retry state, but not the case where a matching requester delivery mirror already proved user-visible delivery.
  • Contributing context (if known): N/A.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/subagent-registry-lifecycle.test.ts.
  • Scenario the test should lock in: parent announce returns no visible delivery while the requester session already contains a matching delivery-mirror completion reply.
  • Why this is the smallest reliable guardrail: it exercises the lifecycle retry/finalize decision directly without requiring a live Slack workspace.
  • Existing test that already covers this (if any): N/A.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Subagent completion runs that already delivered a visible reply to the requester thread should stop retrying/giving up on the later completion announce when the parent correctly replies NO_REPLY.

Diagram (if applicable)

Before:
child delivered reply -> requester delivery mirror exists -> parent announce NO_REPLY -> retry/give-up

After:
child delivered reply -> requester delivery mirror exists -> parent announce NO_REPLY -> cleanup delivered

Security Impact (required)

  • 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
  • If any Yes, explain risk + mitigation: N/A

Security/runtime controls unchanged: message-tool-only delivery policy and channel send enforcement remain in the existing delivery path. This patch only credits a persisted openclaw delivery-mirror in the same requester session after runtime delivery has already committed it.

Repro + Verification

Environment

  • OS: macOS local checkout
  • Runtime/container: Node/Vitest via repo wrapper
  • Model/provider: N/A
  • Integration/channel (if any): mocked requester session history for Slack-like completion lifecycle
  • Relevant config (redacted): N/A

Steps

  1. Simulate a completed expectsCompletionMessage subagent run.
  2. Make the parent announce report delivered: false with completion agent did not produce a visible reply.
  3. Return a matching requester delivery-mirror message from chat.history.

Expected

  • Cleanup finalizes as delivered and does not schedule pending final delivery retries.

Actual

  • The new regression passes with delivered status, cleared error state, no pending final delivery, no retry count, and no give-up log.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Verification run output:

git diff --check
# passed with no output

node scripts/run-vitest.mjs src/agents/subagent-registry-lifecycle.test.ts
Test Files  2 passed (2)
Tests  46 passed (46)

pnpm check:changed
Found 0 warnings and 0 errors.
runtime-sidecar-loaders: local runtime sidecar loaders look OK.
Import cycle check: 0 runtime value cycle(s).

Human Verification (required)

  • Verified scenarios: matching requester delivery mirror credits a failed parent announce; existing failure bookkeeping still remains covered.
  • Edge cases checked: matching uses normalized visible text and requires role: assistant, provider: openclaw, and model: delivery-mirror in the same requester session history.
  • What you did not verify: live Slack end-to-end delivery.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

AI-assisted: yes.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: accidentally crediting the wrong transcript entry.
    • Mitigation: only requester-session openclaw/delivery-mirror assistant messages with matching normalized completion text are credited.
  • Out-of-scope follow-up: live Slack proof for the original redacted production run remains outside this small code/test patch.

Made with Cursor

Changed files

  • src/agents/subagent-registry-lifecycle.test.ts (modified, +112/-0)
  • src/agents/subagent-registry-lifecycle.ts (modified, +76/-4)

Code Example

<slack-message-ts> in thread <slack-thread-ts>

---

NO_REPLY

---

Subagent announce give up (retry-limit) run=<run-id> ... retries=3
RAW_BUFFERClick to expand / collapse

Summary

For expectsCompletionMessage=true subagent runs, OpenClaw can mark completion delivery as failed even when the child already delivered and verified the Slack completion reply. The later requester-session announce can correctly return NO_REPLY to avoid duplicate user-visible output, but the subagent registry treats that as an undelivered completion and retries until retry-limit.

Environment

  • OpenClaw: 2026.5.12 (f066dd2)
  • Gateway: macOS LaunchAgent
  • Requester channel: Slack thread

Observed

In one inspected run:

  • Run: <run-id>
  • Slack thread: <slack-thread-ts>
  • Child session: <child-session-jsonl>
  • Parent session: <parent-session-jsonl>

The child posted the required Slack thread reply and verified the thread contained it:

<slack-message-ts> in thread <slack-thread-ts>

The parent transcript independently showed the same user-visible reply as a delivery-mirror assistant message before the internal completion event. The parent session then received the internal subagent completion event and answered exactly:

NO_REPLY

It made no message(send) call because the Slack reply was already present. Despite that, the registry retried the completion announce three times and logged:

Subagent announce give up (retry-limit) run=<run-id> ... retries=3

Expected

If a subagent has already committed the completion to the requester channel/thread, the completion should be accounted as delivered. A later parent-mediated NO_REPLY should not turn that successful delivery into a failed announce.

Likely Cause

The completion cleanup path appears to credit only the later announce attempt:

  • subagent-registry-32aElbRE.js calls runSubagentAnnounceFlow(...), then uses didAnnounce = delivery.delivered to decide whether to finalize success or retry.
  • subagent-announce-Cdo94lsz.js sets didAnnounce = delivery.delivered.
  • subagent-announce-delivery-DzsdC5tX.js returns failure for expectsCompletionMessage && shouldDeliverAgentFinal && !hasVisibleGatewayAgentPayload(...).

That misses prior explicit channel delivery evidence from the child/session delivery mirror.

Proposed Fix

Persist and check committed completion-delivery evidence for expectsCompletionMessage runs. If the child already delivered to the requester route/thread, finalize the run as delivered and skip parent announce retries. Keep the existing retry behavior when no such evidence exists.

A regression test should simulate:

  • child/subagent has successful or verified Slack message(send) delivery evidence
  • parent-mediated completion announce returns exact NO_REPLY with no message(send) call
  • cleanup marks delivery as delivered, clears pending final delivery, does not increment announceRetryCount, and does not log Subagent announce give up (retry-limit)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING