openclaw - 💡(How to fix) Fix [Bug]: one-shot cron deleteAfterRun can delete and rebind a live non-default DM session when sessionTarget resolves to current/session:*

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…

For a non-default agent in a live 1:1 Signal DM, one-shot cron jobs created from that conversation with deleteAfterRun: true and sessionTarget: "current" or sessionTarget: "session:<live-dm-key>" can delete the live DM session binding itself. When multiple such reminders fire back-to-back, the live DM key can be rebound from a populated session to one or more fresh empty sessions, and the next normal user reply lands in an empty transcript, making the agent appear to have forgotten the conversation.

Root Cause

JOB_C reported deliveryStatus: "not-delivered" because it used delivery.mode: "none" and sent via the message tool inside the run instead of the direct delivery wrapper.

Fix Action

Fix / Workaround

  • src/agents/tools/cron-tool.ts:520-540

    • stamps cron.add jobs with the caller sessionKey when the job omits one
  • src/cron/normalize.ts:540-569

    • resolves sessionTarget: "current" to session:<actual-session-key>
    • defaults one-shot at jobs to deleteAfterRun: true
  • src/cron/isolated-agent/run.ts:246-274

    • uses the provided session key as the base key and only forces a new session when sessionTarget === "isolated"
  • src/cron/isolated-agent/session.ts:31-47

    • reuses an existing session when forceNew is false and the session is still fresh
  • src/cron/isolated-agent/delivery-dispatch.ts:379-393

    • cleanupDirectCronSessionIfNeeded() issues sessions.delete against params.agentSessionKey
  • src/cron/isolated-agent/delivery-dispatch.ts:664-667

    • direct delivery cleanup runs in a finally block after deliverViaDirect(...)
  • src/agents/tools/cron-tool.test.ts:276-285

    • expects cron.add to stamp the caller session key when missing
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts:445-490

    • expects deleteAfterRun direct cleanup to call sessions.delete on the direct cron session key

Code Example

{
  "id": "JOB_A",
  "deleteAfterRun": true,
  "schedule": { "kind": "at", "at": "2026-04-20T17:55:00.000Z" },
  "sessionTarget": "session:LIVE_DM_KEY",
  "delivery": { "mode": "announce", "channel": "signal", "to": "<redacted>" }
}

---

{
  "id": "JOB_B",
  "deleteAfterRun": true,
  "schedule": { "kind": "at", "at": "2026-04-19T17:55:00.000Z" },
  "sessionTarget": "session:LIVE_DM_KEY",
  "delivery": { "mode": "announce", "channel": "signal", "to": "<redacted>" }
}

---

{
  "id": "JOB_C",
  "deleteAfterRun": true,
  "schedule": { "kind": "at", "at": "2026-04-20T17:55:00.000Z" },
  "sessionTarget": "session:LIVE_DM_KEY",
  "delivery": { "mode": "none" }
}

---

2026-04-20T17:55:01.248Z lane enqueue: lane=session:LIVE_DM_KEY
2026-04-20T17:55:02.156Z embedded run start: runId=SESSION_A sessionId=SESSION_A
2026-04-20T17:55:02.641Z [context-diag] pre-prompt: sessionKey=LIVE_DM_KEY messages=66 sessionFile=.../SESSION_A.jsonl

2026-04-20T17:55:10.871Z gateway:sessions.delete connected
2026-04-20T17:55:14.422Z sessions.delete OK

2026-04-20T17:55:15.185Z lane enqueue: lane=session:LIVE_DM_KEY
2026-04-20T17:55:15.933Z embedded run start: runId=SESSION_B sessionId=SESSION_B
2026-04-20T17:55:16.293Z [context-diag] pre-prompt: sessionKey=LIVE_DM_KEY messages=0 sessionFile=.../SESSION_B.jsonl

2026-04-20T17:55:27.184Z gateway:sessions.delete connected
2026-04-20T17:55:30.670Z sessions.delete OK

2026-04-20T17:55:31.435Z lane enqueue: lane=session:LIVE_DM_KEY
2026-04-20T17:55:32.229Z embedded run start: runId=SESSION_C sessionId=SESSION_C
2026-04-20T17:55:32.617Z [context-diag] pre-prompt: sessionKey=LIVE_DM_KEY messages=0 sessionFile=.../SESSION_C.jsonl
2026-04-20T17:55:35.717Z embedded run tool start: runId=SESSION_C tool=message
2026-04-20T17:55:36.060Z Committed messaging text: tool=message len=67

---

2026-04-20T17:57:52.649Z user: "then how would you propose that section open"
2026-04-20T17:57:55.952Z assistant: "I don't have context on which section you mean — which document or topic are you referring to?"

---

{ "jobId": "JOB_A", "sessionId": "SESSION_A", "sessionKey": "LIVE_DM_KEY", "delivered": true }
{ "jobId": "JOB_B", "sessionId": "SESSION_B", "sessionKey": "LIVE_DM_KEY", "delivered": true }
{ "jobId": "JOB_C", "sessionId": "SESSION_C", "sessionKey": "LIVE_DM_KEY", "delivered": false }
RAW_BUFFERClick to expand / collapse

Anonymization map

  • AGENT_A: affected non-default agent
  • USER_A: the human chatting with AGENT_A
  • LIVE_DM_KEY: agent:<agent-a>:signal:default:direct:<redacted-recipient>
  • SESSION_A: populated live DM session before reminder execution
  • SESSION_B: fresh session created after the first delete
  • SESSION_C: fresh session created after the second delete; this remained live afterward
  • JOB_A, JOB_B, JOB_C: three one-shot reminder jobs due at the same minute

Bug type

Behavior bug

Beta release blocker

No

Summary

For a non-default agent in a live 1:1 Signal DM, one-shot cron jobs created from that conversation with deleteAfterRun: true and sessionTarget: "current" or sessionTarget: "session:<live-dm-key>" can delete the live DM session binding itself. When multiple such reminders fire back-to-back, the live DM key can be rebound from a populated session to one or more fresh empty sessions, and the next normal user reply lands in an empty transcript, making the agent appear to have forgotten the conversation.

Steps to reproduce

  1. Start a live direct-message conversation with a non-default agent so the active session key is a real channel session such as LIVE_DM_KEY.
  2. From that conversation, create a one-shot at cron job with payload.kind: "agentTurn", deleteAfterRun: true, and either:
    • sessionTarget: "current", or
    • sessionTarget: "session:<live-dm-key>"
  3. Ensure the created job normalizes/persists to sessionTarget: "session:<live-dm-key>".
  4. Create at least one more one-shot job targeting the same minute and same live DM session key.
  5. Let the jobs fire. In the observed incident:
    • two jobs used direct-delivery cleanup paths that issued sessions.delete on the live DM key
    • a third job used delivery.mode: "none" and sent via the message tool inside the run, leaving the newest fresh session bound as the live DM session
  6. Send a normal follow-up user message in the DM after the reminders complete.

Expected behavior

One-shot cron reminders should not delete or rebind the live user DM conversation. They should either:

  • run in an isolated or ephemeral working session and clean up only that session, or
  • reuse the live session without deleting its binding afterward

The next ordinary user reply in the DM should continue in the pre-existing conversation with full context intact.

Actual behavior

The live DM session key was rebound across three reminder runs:

  • SESSION_A had normal prior context (messages=66)
  • after JOB_A, sessions.delete ran against LIVE_DM_KEY
  • JOB_B then started in fresh SESSION_B with messages=0
  • after JOB_B, sessions.delete ran again against LIVE_DM_KEY
  • JOB_C then started in fresh SESSION_C with messages=0
  • JOB_C used the message tool and did not trigger another observed cleanup, so SESSION_C remained bound as the live DM session

The next normal user message then landed in SESSION_C, and the agent replied:

"I don't have context on which section you mean — which document or topic are you referring to?"

This was a visible context loss from the user's perspective even though the original transcript still existed in archived form.

OpenClaw version

Base release: v2026.4.12

Observed install: openclaw:local Docker image built from a local checkout at commit 1c0672b74f66038fd4ee76fbf1c21715887149d8, with local overrides present on top of that base release.

Operating system

Host: Ubuntu 24.04.3 LTS

Container: Debian GNU/Linux 12 (bookworm)

Install method

Docker Compose (openclaw:local gateway container)

Model

Anthropic Claude Sonnet 4.6

Provider / routing chain

openclaw Docker gateway -> anthropic

Additional provider/model setup details

  • Channel: Signal direct message
  • Agent type: non-default agent
  • Message channel observed in logs: signal

Logs, screenshots, and evidence

1. Reminder creation evidence

Observed from archived session checkpoints for AGENT_A.

JOB_A was created from the live DM and persisted with the live DM session target directly:

{
  "id": "JOB_A",
  "deleteAfterRun": true,
  "schedule": { "kind": "at", "at": "2026-04-20T17:55:00.000Z" },
  "sessionTarget": "session:LIVE_DM_KEY",
  "delivery": { "mode": "announce", "channel": "signal", "to": "<redacted>" }
}

JOB_B was first attempted with sessionTarget: "main" for a non-default agent, failed validation, then was recreated with sessionTarget: "current". The persisted tool result normalized to the same live DM session key:

{
  "id": "JOB_B",
  "deleteAfterRun": true,
  "schedule": { "kind": "at", "at": "2026-04-19T17:55:00.000Z" },
  "sessionTarget": "session:LIVE_DM_KEY",
  "delivery": { "mode": "announce", "channel": "signal", "to": "<redacted>" }
}

Later in the same session, JOB_B was updated so it also fired on 2026-04-20T17:55:00.000Z.

JOB_C was created from the same live DM with sessionTarget: "current" and delivery.mode: "none", but the persisted tool result again normalized to the live DM session key:

{
  "id": "JOB_C",
  "deleteAfterRun": true,
  "schedule": { "kind": "at", "at": "2026-04-20T17:55:00.000Z" },
  "sessionTarget": "session:LIVE_DM_KEY",
  "delivery": { "mode": "none" }
}

2. Runtime timeline from gateway logs

Sanitized from gateway logs on 2026-04-20:

2026-04-20T17:55:01.248Z lane enqueue: lane=session:LIVE_DM_KEY
2026-04-20T17:55:02.156Z embedded run start: runId=SESSION_A sessionId=SESSION_A
2026-04-20T17:55:02.641Z [context-diag] pre-prompt: sessionKey=LIVE_DM_KEY messages=66 sessionFile=.../SESSION_A.jsonl

2026-04-20T17:55:10.871Z gateway:sessions.delete connected
2026-04-20T17:55:14.422Z sessions.delete OK

2026-04-20T17:55:15.185Z lane enqueue: lane=session:LIVE_DM_KEY
2026-04-20T17:55:15.933Z embedded run start: runId=SESSION_B sessionId=SESSION_B
2026-04-20T17:55:16.293Z [context-diag] pre-prompt: sessionKey=LIVE_DM_KEY messages=0 sessionFile=.../SESSION_B.jsonl

2026-04-20T17:55:27.184Z gateway:sessions.delete connected
2026-04-20T17:55:30.670Z sessions.delete OK

2026-04-20T17:55:31.435Z lane enqueue: lane=session:LIVE_DM_KEY
2026-04-20T17:55:32.229Z embedded run start: runId=SESSION_C sessionId=SESSION_C
2026-04-20T17:55:32.617Z [context-diag] pre-prompt: sessionKey=LIVE_DM_KEY messages=0 sessionFile=.../SESSION_C.jsonl
2026-04-20T17:55:35.717Z embedded run tool start: runId=SESSION_C tool=message
2026-04-20T17:55:36.060Z Committed messaging text: tool=message len=67

Important observations from the timeline:

  • the jobs were serialized on the same lane; this was not a simultaneous race
  • SESSION_A clearly had existing history (messages=66)
  • SESSION_B and SESSION_C were both fresh (messages=0)
  • the two observed sessions.delete calls happened between the three runs on the same live DM key
  • the third run used the message tool inside the agent turn and left SESSION_C as the live binding

3. User-visible context loss

The next ordinary user turn after the reminder cascade was:

2026-04-20T17:57:52.649Z user: "then how would you propose that section open"
2026-04-20T17:57:55.952Z assistant: "I don't have context on which section you mean — which document or topic are you referring to?"

That exchange happened in SESSION_C, the fresh reminder-created session.

4. Cron run records

Sanitized from cron run records:

{ "jobId": "JOB_A", "sessionId": "SESSION_A", "sessionKey": "LIVE_DM_KEY", "delivered": true }
{ "jobId": "JOB_B", "sessionId": "SESSION_B", "sessionKey": "LIVE_DM_KEY", "delivered": true }
{ "jobId": "JOB_C", "sessionId": "SESSION_C", "sessionKey": "LIVE_DM_KEY", "delivered": false }

JOB_C reported deliveryStatus: "not-delivered" because it used delivery.mode: "none" and sent via the message tool inside the run instead of the direct delivery wrapper.

Impact and severity

  • Affected users/systems/channels: non-default agents in live Signal direct-message sessions using one-shot cron reminders created from the current conversation
  • Severity: High for affected users because it breaks continuity in the live DM and makes the agent appear to forget active work
  • Frequency: Observed 1/1 time in the captured incident under the described conditions
  • Consequence: the next normal user message lands in a fresh reminder-created session; original context is no longer available in the live thread unless the session binding is manually restored

Additional information

Observed source surfaces to inspect

These are the current local source locations that appear directly relevant to the captured behavior:

  • src/agents/tools/cron-tool.ts:520-540
    • stamps cron.add jobs with the caller sessionKey when the job omits one
  • src/cron/normalize.ts:540-569
    • resolves sessionTarget: "current" to session:<actual-session-key>
    • defaults one-shot at jobs to deleteAfterRun: true
  • src/cron/isolated-agent/run.ts:246-274
    • uses the provided session key as the base key and only forces a new session when sessionTarget === "isolated"
  • src/cron/isolated-agent/session.ts:31-47
    • reuses an existing session when forceNew is false and the session is still fresh
  • src/cron/isolated-agent/delivery-dispatch.ts:379-393
    • cleanupDirectCronSessionIfNeeded() issues sessions.delete against params.agentSessionKey
  • src/cron/isolated-agent/delivery-dispatch.ts:664-667
    • direct delivery cleanup runs in a finally block after deliverViaDirect(...)

Existing test coverage gap

Current tests appear to encode the two risky halves separately, but not the combined live-DM case:

  • src/agents/tools/cron-tool.test.ts:276-285
    • expects cron.add to stamp the caller session key when missing
  • src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts:445-490
    • expects deleteAfterRun direct cleanup to call sessions.delete on the direct cron session key

What appears to be missing is a regression that covers:

  • non-default live DM session
  • sessionTarget: "current" or sessionTarget: "session:<live-dm-key>"
  • one-shot at job with deleteAfterRun: true
  • direct delivery or mixed direct/message-tool delivery
  • verification that cleanup does not delete or rebind the live user DM session

Related upstream context

Potentially related history to review while triaging:

  • #17905 non-default-agent cron routing bugs
  • #61426 sessionTarget: isolated not honored; cron messages accumulated in the main session
  • #13900 feature request for ephemeral cron sessions
  • #67718 / #67807 deleteAfterRun direct-delivery cleanup changes

Appendix: minimal issue summary for triage

Three one-shot reminders were created from a live non-default Signal DM. All three normalized to the same real live DM session key, and all three had deleteAfterRun: true. The first reminder ran in the real conversation (messages=66), then sessions.delete was issued on that live DM key. The second reminder then ran in a fresh empty session (messages=0), and another sessions.delete followed. The third reminder then ran in another fresh empty session (messages=0) and remained bound as the live DM thread. The next ordinary user message hit that empty session, and the agent replied that it had no context.

extent analysis

TL;DR

To fix the issue, modify the cron job handling to prevent deletion or rebinding of live DM sessions when deleteAfterRun: true and sessionTarget: "current" or sessionTarget: "session:<live-dm-key>" are used.

Guidance

  • Review the src/cron/isolated-agent/delivery-dispatch.ts file, specifically the cleanupDirectCronSessionIfNeeded() function, to understand how sessions.delete is triggered.
  • Consider adding a check to prevent sessions.delete from running when the session is a live DM session.
  • Examine the src/agents/tools/cron-tool.ts file to see how sessionTarget is handled and how it can be modified to avoid rebinding live DM sessions.
  • Look into adding a test case to cover the scenario of a non-default live DM session with sessionTarget: "current" or sessionTarget: "session:<live-dm-key>" and deleteAfterRun: true.

Example

No code example is provided as the issue requires a deeper understanding of the codebase and a more complex solution.

Notes

The fix may involve modifying the cron job handling to use ephemeral sessions or to reuse existing sessions without deleting them. Additionally, the issue may be related to previous bugs or feature requests, such as #17905, #61426, #13900, #67718, and #67807, which should be reviewed for context.

Recommendation

Apply a workaround to prevent sessions.delete from running on live DM sessions when deleteAfterRun: true and sessionTarget: "current" or sessionTarget: "session:<live-dm-key>" are used, until a permanent fix can be implemented.

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…

FAQ

Expected behavior

One-shot cron reminders should not delete or rebind the live user DM conversation. They should either:

  • run in an isolated or ephemeral working session and clean up only that session, or
  • reuse the live session without deleting its binding afterward

The next ordinary user reply in the DM should continue in the pre-existing conversation with full context intact.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING