Skip to content

fix(heartbeat): clear pendingFinalDelivery* on send success#83187

Merged
vincentkoc merged 15 commits into
openclaw:mainfrom
agocs:fix/heartbeat-clear-pending-final-delivery
Jun 28, 2026
Merged

fix(heartbeat): clear pendingFinalDelivery* on send success#83187
vincentkoc merged 15 commits into
openclaw:mainfrom
agocs:fix/heartbeat-clear-pending-final-delivery

Conversation

@agocs

@agocs agocs commented May 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #83184.

The heartbeat send path in src/infra/heartbeat-runner.ts:2011-2022 wrote lastHeartbeatText / lastHeartbeatSentAt after a successful sendDurableMessageBatch but never nulled the pendingFinalDelivery* recovery fields. A heartbeat-driven agent run that previously set pendingFinalDelivery: true (via src/auto-reply/reply/agent-runner.ts:2169) therefore left the session permanently stuck: subsequent heartbeats short-circuited in get-reply's redelivery path, the agent was never re-invoked, and the symptom surfaced as silent loss of notifications.

This change extends the existing post-success updateSessionStore block to clear all eight pendingFinalDelivery* fields, mirroring the canonical clearPendingFinalDeliveryAfterSuccess helper at src/auto-reply/reply/dispatch-from-config.ts:365-391 (already invoked from the user-message dispatch path at :1671).

Affected versions: verified on 2026.5.7 through 2026.5.16-beta.2; fix applies against current main (2026.5.17).

Update 1 — pending-final field parity (ClawSweeper review)

ClawSweeper flagged that the shared CLEARED_PENDING_FINAL_DELIVERY_FIELDS set cleared seven of the eight current pendingFinalDelivery* fields, omitting pendingFinalDeliveryIntentId — which the canonical clearPendingFinalDeliveryAfterSuccess does clear and which SessionEntry (src/config/sessions/types.ts:353) defines as durable pending-final metadata. A delivered heartbeat therefore left stale intent metadata behind, inconsistent with the dispatch path the helper claims to mirror.

Fixed by adding pendingFinalDeliveryIntentId: undefined to the constant (src/infra/heartbeat-runner.ts), which both heartbeat clear sites (send-success and duplicate-skip) already spread, so the heartbeat set now matches the canonical eight-field cleanup exactly. Both regression cases now seed pendingFinalDeliveryIntentId and assert it is cleared. Re-verified locally: removing only the new constant line turns both cases RED (expected 'intent-send-success' to be undefined, expected 'intent-duplicate-skip' to be undefined); restored, the file and ClawSweeper's listed acceptance-criteria siblings (3 files / 57 tests) pass; oxfmt --check and oxlint clean on both touched files.

The live Docker trace under Real behavior proof below predates this addition, so its bundle grep and observations describe the original seven …: void 0 lines.

Update 2 — pending-final ownership guard (ClawSweeper re-review)

On re-review ClawSweeper escalated to a P1: the send-success and duplicate-skip clears retired any pendingFinalDelivery whenever the flag/text was set, without checking the current heartbeat run owned it. Confirmed against the code — the 30s defer gate only protects recent substantive pending; heartbeat get-reply does not redeliver substantive pending (src/auto-reply/reply/get-reply.ts:510, "must not replay user-facing pending finals through a different delivery target"); and a message_tool_only/suppressed run does not refresh pending (src/auto-reply/reply/agent-runner.ts:2541, pendingText = suppressDelivery ? "" : …). So an older user-facing final — a delivery-failure recovery marker the get-reply redelivery short-circuit owns (src/auto-reply/reply/dispatch-from-config.ts:3378) — could be erased by a later heartbeat that never satisfied it.

Fixed by gating both clear sites on ownership via a shared heartbeatRunOwnsPendingFinalDelivery helper (src/infra/heartbeat-runner.ts): clear only when pendingFinalDeliveryCreatedAt >= startedAt, i.e. the pending was stamped during this run. The run's own satisfied pending is still cleared (the original fix); an older final the run did not produce is preserved for its own recovery path.

Two regression tests added: an older, unowned pending-final is preserved across both the send-success and duplicate-skip paths. Verified RED against the unconditional clear (expected undefined to be true) → GREEN with the guard; the two original "clears its own pending" cases were updated to seed an owned createdAt and still pass. Full heartbeat-runner suite (16 files / 176 tests) and the sibling src/auto-reply/reply/get-reply.fast-path.test.ts (19 tests) pass; oxfmt --check and oxlint clean on both touched files.

Verification

Commands run locally against this branch on Ubuntu 24.04, Node 22.22.2, pnpm 11.1.0:

  • node scripts/run-vitest.mjs src/infra/heartbeat-runner.clears-pending-final-delivery.test.ts — new regression test, 1 passed (also confirmed RED before the fix landed in the same branch).
  • node scripts/run-vitest.mjs <all 16 src/infra/heartbeat-runner.*.test.ts non-e2e files> — 16 files / 152 tests passed.
  • pnpm check:changed — green: conflict markers, changelog attributions, wildcard re-export guards, duplicate coverage, dependency pin guard, package patch guard, typecheck core, typecheck core tests, lint core (0/0), media download helper guard, runtime sidecar loader guard, runtime import cycles, webhook body guard, pairing store guard, pairing account guard.
  • oxfmt --check and core oxlint — both clean on the two touched files.

The regression file covers two cases. The send-success case seeds five pendingFinalDelivery* fields via the store seeder plus a heartbeat-ack pendingFinalDeliveryText (so the heartbeat-defer check at heartbeat-runner.ts:~1328 does not short-circuit), patches in the three the seeder does not expose (pendingFinalDeliveryLastAttemptAt, pendingFinalDeliveryContext, pendingFinalDeliveryIntentId), runs runHeartbeatOnce with a substantive reply, and asserts all eight fields are undefined afterward — plus that lastHeartbeatText / lastHeartbeatSentAt are written, proving the success branch ran. The duplicate-skip case asserts the same eight-field clear when a responsePrefix-prefixed payload was already delivered, so the run short-circuits in the duplicate-suppression branch without a new send.

Real behavior proof

Behavior addressed: heartbeat send-success path leaves pendingFinalDelivery* set, causing the next heartbeat to skip the agent via the get-reply redelivery short-circuit.

Real environment tested: Docker-deployed gateway on Ubuntu 24.04 (kernel 6.8.0-111-generic). Prior to this patch the gateway was on OpenClaw 2026.5.12; the bug was deterministic there, confirmed by an out-of-band cron job (*/10 * * * *) that nulls the stuck fields whenever pendingFinalDeliveryText matches lastHeartbeatText. Cron log baseline showed one "cleared stuck" entry per hour (e.g., 2026-05-17T15:50:01Z and 2026-05-17T16:50:01Z, both agent:main:main) — i.e., every substantive heartbeat left the session stuck and the cron had to clean up.

Exact steps or command run after this patch:

  1. Checked out this branch (fix/heartbeat-clear-pending-final-delivery, current main + the diff in this PR; reported OpenClaw 2026.5.17).
  2. Built the openclaw image from this branch: DOCKER_BUILDKIT=1 docker build -t openclaw-source:fix-heartbeat-pending-delivery ..
  3. Pointed the deployment stack at that locally built image and redeployed: docker compose build && docker compose down && docker compose up -d. New container came up at 2026-05-17T17:04:33Z.
  4. Confirmed the cleanup block compiled into the runtime bundle: docker compose exec openclaw-gateway grep -c "pendingFinalDeliveryContext: void 0" /app/dist/heartbeat-runner-*.js1 in the send-path bundle (heartbeat-runner-CecDa3Rk.js), 0 in the runtime stub (heartbeat-runner-DPGtQP6X.js). Context grep around lastHeartbeatSentAt: startedAt shows the seven pendingFinalDelivery*: void 0 lines immediately after it, in the same updateSessionStore write that records lastHeartbeatText/lastHeartbeatSentAt.
  5. Watched agent:main:main in the host-mounted sessions.json and the cron log over the next ~2 hours of real heartbeats.

Evidence after fix:

  • New regression test (src/infra/heartbeat-runner.clears-pending-final-delivery.test.ts) goes RED on main (expected true to be undefined at the pendingFinalDelivery assertion) and GREEN with this patch. Sister tests in src/infra/heartbeat-runner.* (16 files / 152 tests) remain green.
  • Live prod heartbeat trace (agent:main:main, host-mounted sessions store, redacted preview):
    • 2026-05-17T17:04:33Z — patched container started (OpenClaw 2026.5.17).
    • 2026-05-17T17:04:41Z — heartbeat scheduler logged heartbeat: started intervalMs=1800000. Subsequent ticks ran but were all HEARTBEAT_OK (silent, no store write), keeping lastHeartbeatSentAt at the pre-redeploy 2026-05-17T16:40:18Z for ~2.5 hours.
    • 2026-05-17T19:10:18Z — first substantive heartbeat after the redeploy. lastHeartbeatSentAt advanced to 1779045018769, lastHeartbeatText written ("One mildly notable item — everything else is promos: …").
    • 2026-05-17T19:11:04Z (≤46s after the send write, well before the next */10 cron tick at 19:20:00Z) — pendingFinalDelivery: None, pendingFinalDeliveryText: "". This clean state is attributable only to the patched send-path write, not to the cron.
    • 2026-05-17T19:20:00Z cron tick — zero new lines in clear-stuck-pending-delivery.log (baseline was 2 lines, post-tick was still 2 lines). On the unpatched image this is the tick that would have logged a "cleared stuck" line, matching the prior 15:50 / 16:50 cadence.

Observed result after fix: a real substantive heartbeat in the deployed gateway now leaves all seven pendingFinalDelivery* fields cleared in the same updateSessionStore write that records lastHeartbeatText/lastHeartbeatSentAt, and the standing cron mitigation no longer finds anything to clean up.

What was not tested:

  • Multi-hour soak: only one substantive heartbeat has been observed post-deploy at the time of writing; a longer window would harden confidence that the cron stays silent across many cycles.
  • Channels other than Telegram: this deployment only uses Telegram for heartbeat delivery, so WhatsApp / Slack send-success paths have not been exercised end-to-end (the unit test covers the post-success store write generically via the test-runtime channel plugin).
  • The pendingFinalDelivery: true recovery branch where final delivery failed and a retry is genuinely needed — that path is still gated by the clearPendingFinalDeliveryAfterSuccess guard in dispatch-from-config.ts; this PR only adds the equivalent clear in the heartbeat send-success path.

@openclaw-barnacle openclaw-barnacle Bot added size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 17, 2026
@clawsweeper

clawsweeper Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 3:06 PM ET / 19:06 UTC.

Summary
The PR clears heartbeat-owned pendingFinalDelivery* recovery fields after heartbeat send success or duplicate suppression and adds regression coverage for owned cleanup and older-state preservation.

PR surface: Source +62, Tests +298. Total +360 across 2 files.

Reproducibility: yes. Current main can store substantive heartbeat output as pendingFinalDelivery and then record visible heartbeat send success without clearing that recovery state; the PR body also reports a Docker deployment where the state cleared only after the patch.

Review metrics: 2 noteworthy metrics.

  • Pending-final cleanup parity: 8 fields, 2 clear sites. The heartbeat cleanup must stay aligned with the canonical dispatch-success recovery field family.
  • Ownership guard coverage: 2 clear sites gated, 4 regression cases. Both heartbeat send-success and duplicate-skip paths need proof for clearing owned state while preserving older recovery state.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #83184
Summary: This PR is the candidate fix for the canonical heartbeat send-success pendingFinalDelivery cleanup issue; duplicate-suppression cleanup was ported here, while broader orphan replay bounds remain adjacent work.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Refresh a short current-head store-backed or live heartbeat proof if maintainers want runtime evidence for the final ownership-guard commit.

Risk before merge

  • [P1] Maintainers should explicitly accept pendingFinalDeliveryCreatedAt >= startedAt as the run-ownership signal; if that contract is wrong, the cleanup could either miss a satisfied heartbeat final or retire unrelated recovery state.
  • [P1] The Docker proof demonstrates the central send-success fix, but the final ownership-guard commit is covered by regression tests rather than a refreshed current-head live soak.
  • [P1] The PR base is behind current main; normal merge queue or rebase gates should validate the exact merge result before landing.

Maintainer options:

  1. Land With CreatedAt Ownership (recommended)
    Merge after maintainers accept pendingFinalDeliveryCreatedAt at or after run start as the heartbeat pending-final ownership contract and required gates pass.
  2. Request Current-Head Runtime Proof
    Ask for a short current-head store-backed or live heartbeat proof if maintainers want runtime evidence for the final ownership-guard commit before merge.
  3. Pause For Stronger Ownership Metadata
    Pause only if maintainers want route- or intent-scoped heartbeat ownership metadata before duplicate-skip cleanup lands.

Next step before merge

  • [P2] The remaining action is maintainer review of the session-state ownership contract and normal merge gates, not an automated repair.

Security
Cleared: The diff changes core heartbeat session cleanup and a colocated test only; no CI, dependency, secret, permission, or supply-chain surface change was found.

Review details

Best possible solution:

Land this PR or an equivalent focused heartbeat-runner fix after maintainer acceptance of the createdAt ownership guard and normal merge gates.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main can store substantive heartbeat output as pendingFinalDelivery and then record visible heartbeat send success without clearing that recovery state; the PR body also reports a Docker deployment where the state cleared only after the patch.

Is this the best way to solve the issue?

Yes. Heartbeat-runner is the narrow path that knows visible heartbeat delivery or duplicate suppression succeeded, and the ownership guard preserves older pending-final recovery state that this heartbeat did not produce.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against eac1d3349c29.

Label changes

Label justifications:

  • P1: The PR fixes a heartbeat/session-state failure that can silently stop later heartbeat model calls or notifications for affected users.
  • merge-risk: 🚨 session-state: The diff changes when persisted pendingFinalDelivery* recovery state is retired after heartbeat delivery is considered satisfied.
  • merge-risk: 🚨 message-delivery: The diff changes heartbeat delivery recovery behavior on send-success and duplicate-suppression paths, affecting whether future messages retry or proceed.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The PR body includes after-fix Docker gateway logs and session-store observations for the central send-success behavior; the later ownership guard is covered by focused regression tests.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix Docker gateway logs and session-store observations for the central send-success behavior; the later ownership guard is covered by focused regression tests.
Evidence reviewed

PR surface:

Source +62, Tests +298. Total +360 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 62 0 +62
Tests 1 298 0 +298
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 360 0 +360

What I checked:

  • Current main still lacks heartbeat cleanup: Current main records lastHeartbeatText and lastHeartbeatSentAt after visible heartbeat delivery, but the same update does not clear any pendingFinalDelivery* recovery fields. (src/infra/heartbeat-runner.ts:2134, eac1d3349c29)
  • Latest release also lacks the cleanup: v2026.6.10 has the same heartbeat dedupe write surface without CLEARED_PENDING_FINAL_DELIVERY_FIELDS or heartbeatRunOwnsPendingFinalDelivery, so the central fix is not shipped yet. (src/infra/heartbeat-runner.ts:2126, aa69b12d0086)
  • PR head adds full pending-final field cleanup: PR head defines all eight recovery fields and uses that set in both duplicate-skip and send-success cleanup paths. (src/infra/heartbeat-runner.ts:1310, 675dcd8ab6e6)
  • PR head gates cleanup by run ownership: The duplicate-suppression clear and send-success write both call heartbeatRunOwnsPendingFinalDelivery, preserving older pending-final recovery state that this heartbeat did not produce. (src/infra/heartbeat-runner.ts:1726, 675dcd8ab6e6)
  • Canonical dispatch helper clears the same field family: The normal final-dispatch success helper clears pendingFinalDelivery, text, timestamps, attempt metadata, context, and intent id after delivery succeeds. (src/auto-reply/reply/dispatch-from-config.ts:741, eac1d3349c29)
  • Pending-final creation is stamped during the agent run: agent-runner writes pendingFinalDeliveryCreatedAt: Date.now() when it captures policy-visible final text, supporting the PR's createdAt ownership guard. (src/auto-reply/reply/agent-runner.ts:2529, eac1d3349c29)

Likely related people:

  • MertBasar0: Commit c240e718e91e added the main-session durable pending-final delivery paths across agent-runner, dispatch, get-reply, session types, and heartbeat-runner. (role: introduced durable pending-final delivery behavior; confidence: high; commits: c240e718e91e; files: src/auto-reply/reply/agent-runner.ts, src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/get-reply.ts)
  • steipete: Commit 1c1253e5af33 fixed ack-only heartbeat pending replay behavior, and recent commit history shows repeated heartbeat-runner maintenance around this surface. (role: adjacent heartbeat pending-delivery contributor; confidence: high; commits: 1c1253e5af33, bbc4bee7a22b, 023a101b9138; files: src/auto-reply/reply/get-reply.ts, src/infra/heartbeat-runner.ts, src/infra/heartbeat-runner.skips-busy-session-lane.test.ts)
  • kesslerio: Commit d00e764e6655 changed heartbeat pending-final replay behavior and established the adjacent invariant that older pending state should be preserved rather than replayed through heartbeat. (role: recent heartbeat pending-delivery contributor; confidence: high; commits: d00e764e6655; files: src/auto-reply/reply/get-reply.ts, src/infra/heartbeat-runner.ts, src/infra/heartbeat-runner.skips-busy-session-lane.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added P1 High-priority user-facing bug, regression, or broken workflow. impact:message-loss Channel message delivery can be lost, duplicated, or misrouted. impact:session-state Session, memory, transcript, context, or agent state can drift or corrupt. labels May 17, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 17, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 17, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 28, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed impact:session-state Session, memory, transcript, context, or agent state can drift or corrupt. impact:message-loss Channel message delivery can be lost, duplicated, or misrouted. labels May 28, 2026
@clawsweeper

clawsweeper Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg: ✨ hatched 🥚 common Clockwork Signal Puff. Rarity: 🥚 common. Trait: hums during re-review.

Details

Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Clockwork Signal Puff in ClawSweeper.
Hatchability:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

About:

  • Eggs appear after real-behavior proof passes. They are collectible flavor only.
  • Review momentum changes the shell state: follow-up work warms it, re-review makes it wobble, and a clean final review lets it hatch.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

Copy link
Copy Markdown
Contributor

Sibling PR #87583 was closed as superseded by this canonical cleanup path, which makes sense for the send-success portion. One remaining gap from #87583 may still be worth porting here: duplicate-suppression cleanup.

#83187 clears pendingFinalDelivery* after the successful send path. #87583 additionally clears matching stale pending state when the duplicate branch returns before the send-success store write, using this predicate:

  • pendingFinalDelivery === true
  • trimmed pendingFinalDeliveryText exactly matches the normalized heartbeat text
  • duplicate suppression already proved the same payload was delivered recently via lastHeartbeatText / lastHeartbeatSentAt

I ran a disposable local OpenClaw source-runtime proof on #87583 (codex/fix-heartbeat-pending-replay, 9497a7a032cf) with a temporary config/workspace/session store and the actual runHeartbeatOnce + sendDurableMessageBatch heartbeat path. The model reply was stubbed to deterministic text so the proof isolates delivery/session-state behavior.

Relevant duplicate-suppression proof:

{
  "scenario": "duplicate-skip",
  "result": { "status": "ran", "durationMs": 51 },
  "sendCallCount": 0,
  "before": {
    "pendingFinalDelivery": true,
    "pendingFinalDeliveryText": "PR87583 duplicate-skip heartbeat payload",
    "pendingFinalDeliveryCreatedAt": 1779957646044,
    "pendingFinalDeliveryLastAttemptAt": 1779957706044,
    "pendingFinalDeliveryAttemptCount": 2,
    "pendingFinalDeliveryLastError": "redacted prior delivery failure",
    "pendingFinalDeliveryContext": { "channel": "telegram", "to": "proof-chat" },
    "pendingFinalDeliveryIntentId": "intent-duplicate-skip",
    "lastHeartbeatText": "PR87583 duplicate-skip heartbeat payload",
    "lastHeartbeatSentAt": 1779957706044
  },
  "after": {
    "pendingFinalDelivery": "<absent>",
    "pendingFinalDeliveryText": "<absent>",
    "pendingFinalDeliveryCreatedAt": "<absent>",
    "pendingFinalDeliveryLastAttemptAt": "<absent>",
    "pendingFinalDeliveryAttemptCount": "<absent>",
    "pendingFinalDeliveryLastError": "<absent>",
    "pendingFinalDeliveryContext": "<absent>",
    "pendingFinalDeliveryIntentId": "<absent>",
    "lastHeartbeatText": "PR87583 duplicate-skip heartbeat payload",
    "lastHeartbeatSentAt": 1779957706044
  },
  "clearedAllPendingFields": true,
  "deliveryEvidence": "duplicate suppression skipped outbound send because lastHeartbeatText matched within 24h"
}

If maintainers want this PR to remain the single canonical fix, please consider porting the duplicate-suppression cleanup and regression test from #87583 rather than reopening the sibling PR.

@RomneyDa

Copy link
Copy Markdown
Member

Heads up: this PR needs to be updated against current main before the new required Dependency Guard check can pass.

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@agocs

agocs commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

Button clicked, thanks @RomneyDa

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
Heartbeat-driven agent runs can leave pendingFinalDelivery set on the
session; runHeartbeatOnce wrote lastHeartbeatText/lastHeartbeatSentAt on
send success but never cleared the recovery fields, so the next heartbeat
hit the get-reply redelivery short-circuit and skipped the agent. Extend
the existing post-success updateSessionStore block to null all seven
pendingFinalDelivery* fields, mirroring clearPendingFinalDeliveryAfterSuccess
in dispatch-from-config.ts.

Refs: openclaw#83184
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 18, 2026
Add the eighth pending-final field to CLEARED_PENDING_FINAL_DELIVERY_FIELDS so
the heartbeat send-success and duplicate-skip paths match the canonical
clearPendingFinalDeliveryAfterSuccess cleanup; a delivered heartbeat no longer
leaves stale intent metadata behind. Seed and assert the field in both
regression cases.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SkPieH67337ieQPWVXQgMD
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 20, 2026
@agocs

agocs commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot removed the rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. label Jun 20, 2026
agocs and others added 4 commits June 21, 2026 19:44
The send-success and duplicate-skip clears retired any pendingFinalDelivery
unconditionally, so a heartbeat could erase an older, unsatisfied user-facing
final it never produced (e.g. a message_tool_only run that does not refresh
pending). Gate both clears on ownership via a shared helper: clear only when
pendingFinalDeliveryCreatedAt is at/after the run start. Add regression tests
covering the older-pending preserve case on both clear paths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SkPieH67337ieQPWVXQgMD
@agocs

agocs commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Heartbeat-driven agent replies leave pendingFinalDelivery stuck, blocking subsequent heartbeats

5 participants