Skip to content

fix(cron): mirror announce delivery into destination session#80786

Merged
steipete merged 5 commits into
openclaw:mainfrom
cavit99:fix/cron-channel-session-mirror
May 15, 2026
Merged

fix(cron): mirror announce delivery into destination session#80786
steipete merged 5 commits into
openclaw:mainfrom
cavit99:fix/cron-channel-session-mirror

Conversation

@cavit99

@cavit99 cavit99 commented May 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #80468.

Cron direct announce delivery could successfully send a message to a channel while leaving the destination channel session transcript unaware of that assistant message. Later sessions_history reads from the channel session could therefore miss cron reports that the user had already received in chat.

This PR updates isolated cron direct delivery to:

  • resolve the outbound destination session before sending
  • ensure the resolved session entry exists for first-contact outbound deliveries
  • append the successfully delivered assistant output into that destination session transcript
  • keep the transcript mirror best-effort so mirror failures do not turn successful delivery into a failed cron run
  • avoid duplicate main-session records when cron awareness queueing is already the intended main-session record
  • skip deleteAfterRun cleanup for non-cron session keys

It also tightens fallback outbound session classification for resolver-less channels: plugin-owned target parsing wins first, and core only handles explicit semantic prefixes like user:, channel:, and group:.

Related: #74913 covers adjacent cron transcript mirroring work for a narrower session:<id> path. This PR covers destination channel-session mirroring for cron direct announce delivery.

Real behavior proof

  • Behavior or issue addressed:
    Cron direct announce delivery to a WhatsApp DM should be visible in that WhatsApp DM session transcript / sessions_history.

  • Real environment tested:
    macOS mac-mini running OpenClaw installed from the PR branch tarball at SHA 97c6640688 (openclaw-2026.5.10-beta.1.tgz, built via pnpm install --frozen-lockfile && pnpm run build && pnpm pack). WhatsApp channel enabled, cfg.session.dmScope = "per-channel-peer". Base release before the swap: [email protected].

  • Exact steps or command run after this patch:

    # Install fix tarball (built locally via `pnpm pack` from the PR branch)
    npm install -g ./openclaw-2026.5.10-beta.1.tgz
    
    # Beta-channel migration only — this box came from [email protected] stable.
    # NOT a fix requirement; needed because `2026.5.10-beta.1` externalised the codex
    # plugin from the bundled extensions/ tree. Skip on a clean beta install that
    # already has @openclaw/codex resolved.
    openclaw plugins install @openclaw/codex
    
    # Restart gateway
    launchctl unload ~/Library/LaunchAgents/ai.openclaw.gateway.plist
    launchctl enable gui/$(id -u)/ai.openclaw.gateway
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
    
    # Create one-shot synthetic cron with a unique recognizable token
    openclaw cron add \
      --name "REPRO-after-fix" \
      --agent main --announce \
      --channel whatsapp --to "+15551234567" \
      --account default --session isolated \
      --session-key "agent:main:whatsapp:direct:+15551234567" \
      --at "1m" --delete-after-run --light-context --thinking low \
      --timeout-seconds 60 --tools exec \
      --message "...emit exactly REPRO_TOKEN_AFTER_FIX_7a4611d7..."
    
    # After cron finishes: verify run log + grep destination session
    jq '{status,delivered,deliveryStatus,delivery,summary,sessionKey}' \
      ~/.openclaw/cron/runs/<cron-id>.jsonl
    
    grep -c "REPRO_TOKEN_AFTER_FIX_7a4611d7" \
      ~/.openclaw/agents/main/sessions/<wa-dm-session-id>.jsonl
  • Evidence after fix:

    OpenClaw version / package source:

    $ jq -r '.name + "@" + .version' /Users/<redacted>/.npm-global/lib/node_modules/openclaw/package.json
    [email protected]
    # installed from ./openclaw-2026.5.10-beta.1.tgz built at SHA 97c6640688
    

    Synthetic token used: REPRO_TOKEN_AFTER_FIX_7a4611d7

    Cron run succeeded + WhatsApp delivery succeeded from ~/.openclaw/cron/runs/<job-id>.jsonl:

    {
      "status": "ok",
      "delivered": true,
      "deliveryStatus": "delivered",
      "delivery": {
        "resolved": {
          "ok": true,
          "channel": "whatsapp",
          "to": "+15551234567",
          "accountId": "default",
          "source": "explicit"
        },
        "fallbackUsed": true,
        "delivered": true
      },
      "summary": "REPRO_TOKEN_AFTER_FIX_7a4611d7",
      "sessionKey": "agent:main:cron:<job-id>:run:<worker-id>"
    }

    sessions_history / direct transcript grep finds the token in the resolved WhatsApp DM session:

    $ grep -c "REPRO_TOKEN_AFTER_FIX_7a4611d7" \
        ~/.openclaw/agents/main/sessions/<wa-dm-session-id>.jsonl
    1
    

    The single entry, inspected:

    ts=2026-05-11T22:07:48
    type=message  role=assistant
    text: REPRO_TOKEN_AFTER_FIX_7a4611d7
    

    The DM session id corresponds to session key agent:main:whatsapp:direct:+15551234567, the resolved destination session, not the isolated cron worker session.

  • Observed result after fix:
    The fresh token delivered by cron appears in the destination WhatsApp DM session transcript as a role=assistant mirror entry. Hit count is 1, versus 0 hits in the same session under the before-fix stable repro on [email protected], where the identical synthetic cron token was present only in the cron run log and the auto-deleted isolated worker session.

  • What was not tested:
    Other channels (Discord, Telegram, Slack) and dmScope: "main" / non-per-channel-peer setups were not live-tested on this box. They are covered by targeted unit/seam tests in src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts and src/infra/outbound/outbound-session.test.ts.

  • Before evidence:
    Cron-delivered messages not visible in channel session context #80468 (comment) — same shape of synthetic cron on [email protected] produced delivered: true with 0 hits of the unique token in the same destination DM session jsonl.

Root Cause

  • Root cause:
    Cron direct announce delivery sent outbound payloads using the cron run session context without appending the successfully delivered assistant output into the resolved destination channel session transcript.

  • Missing detection / guardrail:
    Existing tests covered successful delivery and awareness queueing, but did not assert that explicit channel cron delivery mirrors into the destination session used by later sessions_history reads.

  • Contributing context:
    Main-session awareness is a separate system-event path and does not replace the destination channel transcript entry for per-channel-peer DM sessions.

Regression Test Plan

  • Coverage level that should have caught this:

    • Seam / integration test
  • Target test or file:
    src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts and src/infra/outbound/outbound-session.test.ts

  • Scenario the test should lock in:
    Isolated cron direct announce delivery resolves the destination channel session, bootstraps the session entry when needed, and appends the delivered assistant text to that destination session only after successful delivery.

  • Why this is the smallest reliable guardrail:
    It exercises the cron dispatch/outbound-session seam without requiring live channel credentials, while the live proof above verifies the real WhatsApp behavior end to end.

User-visible / Behavior Changes

Cron direct announce messages are now visible in the destination channel session transcript after successful delivery, so later turns on that channel can see what the cron job sent.

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

Verification

  • Before-fix live repro on [email protected]: synthetic cron delivery reached WhatsApp, but the fresh token had 0 hits in the WhatsApp DM session transcript.
  • After-fix live proof above: synthetic cron delivery reached WhatsApp and the fresh token had 1 hit in the resolved WhatsApp DM session transcript as role=assistant.
  • pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/infra/outbound/outbound-session.test.ts -- --reporter=verbose
  • pnpm exec oxfmt --check --threads=1 src/cron/isolated-agent/delivery-dispatch.ts src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/infra/outbound/outbound-session.ts src/infra/outbound/outbound-session.test.ts src/infra/outbound/outbound-session.test-helpers.ts
  • git diff --check
  • pnpm check:changed

@openclaw-barnacle openclaw-barnacle Bot added size: XL triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 11, 2026
@clawsweeper

clawsweeper Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR resolves cron direct announce destination sessions, mirrors successful delivered output into that transcript, tightens fallback outbound target classification, and adds cron/outbound regression tests plus a changelog entry.

Reproducibility: yes. Source inspection on current main shows direct cron delivery uses params.agentSessionKey for outbound session context, and the PR body provides before/after terminal proof where the delivered WhatsApp token was absent before and present once after the patch.

Real behavior proof
Sufficient (terminal): The PR body includes terminal/log proof from a real WhatsApp cron delivery after the patch showing the unique token delivered and present once in the destination DM transcript, with linked before evidence showing zero hits on the prior release.

Next step before merge
No repair lane is needed; the patch has sufficient proof and no blocking findings, so the remaining action is normal maintainer review and CI-based merge judgment.

Security
Cleared: The diff is limited to cron/outbound TypeScript, tests, and changelog, with no dependency, workflow, install, publish, secret-handling, or third-party code execution changes found.

Review details

Best possible solution:

Land this coordinated cron transcript mirror fix after CI and maintainer review, then resolve the narrower related work at #74913 and the linked cron-session issues against the merged behavior.

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

Yes. Source inspection on current main shows direct cron delivery uses params.agentSessionKey for outbound session context, and the PR body provides before/after terminal proof where the delivered WhatsApp token was absent before and present once after the patch.

Is this the best way to solve the issue?

Yes. Resolving the destination session before send and appending a best-effort mirror only after successful delivery is the narrow maintainable fix; the duplicate guard preserves main-session awareness policy.

Acceptance criteria:

  • pnpm test src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/infra/outbound/outbound-session.test.ts -- --reporter=verbose
  • pnpm exec oxfmt --check --threads=1 src/cron/isolated-agent/delivery-dispatch.ts src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/infra/outbound/outbound-session.ts src/infra/outbound/outbound-session.test.ts src/infra/outbound/outbound-session.test-helpers.ts
  • pnpm check:changed

What I checked:

  • Current main source gap: Current main builds the direct cron outbound session from params.agentSessionKey, so a successful channel send can remain tied to the isolated/run session instead of the destination channel session; the only post-send persistence in this block is main-session awareness queueing. (src/cron/isolated-agent/delivery-dispatch.ts:646, daef8e73fc92)
  • PR implementation path: The PR head resolves a deliverySessionKey, builds outbound delivery with that key, then appends a best-effort transcript mirror only after delivered is true. (src/cron/isolated-agent/delivery-dispatch.ts:911, bec9d7d823a7)
  • Mirror append guard: The PR head projects the effective outbound payload for transcript text, suppresses duplicate main-session mirrors when awareness queueing is the intended record, and keeps mirror failures non-fatal. (src/cron/isolated-agent/delivery-dispatch.ts:1020, bec9d7d823a7)
  • Regression coverage: The PR adds focused coverage for mirroring isolated cron direct delivery into the resolved WhatsApp destination session, preserving delivery success when mirror append fails, and keeping custom-session cron mirrors on the custom session. (src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts:1372, bec9d7d823a7)
  • Fallback routing coverage: The PR changes resolver-less outbound routing so plugin parsing and explicit semantic prefixes determine direct/group/channel fallback classification, with tests for group, user, thread, and plugin-parsed targets. (src/infra/outbound/outbound-session.ts:56, bec9d7d823a7)
  • Real behavior proof: The PR body supplies after-fix terminal/log proof from a packaged PR build: a WhatsApp cron direct delivery succeeded and grep found the unique token once in the resolved destination DM transcript; linked before evidence on [email protected] found zero destination-session hits for the same shape. (bec9d7d823a7)

Likely related people:

  • steipete: Recent current-main history touches cron direct delivery, cron awareness authority, outbound helpers, and the PR branch also includes awareness-policy preservation and changelog commits by this contributor. (role: recent area contributor; confidence: high; commits: c0fe7ab34ab8, a4b17d65a8ff, 741005001b98; files: src/cron/isolated-agent/delivery-dispatch.ts, src/infra/outbound/outbound-session.ts, CHANGELOG.md)
  • MonkeyLeeT: The deleteAfterRun direct-delivery cleanup in recent history is adjacent to this PR's non-cron session cleanup guard. (role: adjacent cron delivery contributor; confidence: medium; commits: 9501656a8eee; files: src/cron/isolated-agent/delivery-dispatch.ts)
  • drobison00: Recent cron awareness work preserved untrusted labels on the same isolated cron awareness pathway that this PR keeps separate from destination transcript mirroring. (role: adjacent cron awareness contributor; confidence: medium; commits: f61896b03cc7; files: src/cron/isolated-agent/delivery-dispatch.ts)
  • gumadeiras: Recent outbound-session history includes channel-plugin session routing and owner-store metadata changes in the shared route resolver that this PR extends for fallback classification. (role: outbound session routing contributor; confidence: medium; commits: 4079de21ce5c, e29d3709696b; files: src/infra/outbound/outbound-session.ts)

Remaining risk / open question:

  • I did not run tests in this read-only review; merge should still wait for the relevant CI or maintainer-run targeted checks.
  • The live proof covers WhatsApp per-channel-peer delivery; other channels rely on the added seam tests and shared outbound-session contracts.

Codex review notes: model gpt-5.5, reasoning high; reviewed against daef8e73fc92.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 11, 2026
@cavit99
cavit99 marked this pull request as ready for review May 11, 2026 22:15
@cavit99

cavit99 commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

The after-fix proof is already in the body under Real behavior proof → Evidence after fix: redacted terminal output for the cron run-log JSON, grep -c returning 1 in the destination DM session jsonl, and the inspected role=assistant entry showing the mirrored token.

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 11, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 11, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 11, 2026
@cavit99

cavit99 commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

Note on the latest test-only commit (51c5f0b): after marking the PR ready, CI exposed a checks-node-core-fast failure in the broad unit-fast shard. The failing files were src/image-generation/provider-registry.test.ts and src/video-generation/provider-registry.test.ts.

Those tests used static registry imports while mocking capability-provider-runtime; under the non-isolated fast shard, module order could make them see real built-in providers instead of the mocked provider list. The commit changes only those tests to import the registry after resetting modules, matching the mocked boundary. No image/video production code changed.

Happy to split or drop this test-only commit if maintainers prefer the PR to stay strictly limited to the cron/session changes.

@cavit99
cavit99 force-pushed the fix/cron-channel-session-mirror branch from 51c5f0b to 22b3def Compare May 15, 2026 12:29
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@cavit99

cavit99 commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main; folded in two small cleanups (preserve media filenames in awareness-mirror text; add thread: to fallback prefix strip). 22b3def611

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@cavit99
cavit99 force-pushed the fix/cron-channel-session-mirror branch from 22b3def to 3e6b99e Compare May 15, 2026 12:54
@cavit99

cavit99 commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Caught up to c0fe7ab (system-event authority field landed an hour ago); test assertions updated to include forceSenderIsOwnerFalse: true. 3e6b99e1c5

Re-review progress:

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@steipete
steipete force-pushed the fix/cron-channel-session-mirror branch from 3e6b99e to 1bf70b5 Compare May 15, 2026 16:21
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@steipete
steipete force-pushed the fix/cron-channel-session-mirror branch from 1bf70b5 to bec9d7d Compare May 15, 2026 17:40
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
@steipete
steipete merged commit 48b4e5b into openclaw:main May 15, 2026
95 of 96 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Landed via rebase onto main.

  • Gate: git diff --check; node scripts/run-vitest.mjs src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts src/infra/outbound/outbound-session.test.ts src/image-generation/provider-registry.test.ts src/video-generation/provider-registry.test.ts --reporter=verbose (4 files, 98 tests passed)
  • Source head: bec9d7d
  • Landed commit: 48b4e5b

Thanks @cavit99!

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

Labels

proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cron-delivered messages not visible in channel session context

2 participants