Scheduled wake-ups fire into wrong session when two sessions share a project dir

Resolved 💬 3 comments Opened Apr 18, 2026 by Alexey-Rivkin Closed May 31, 2026

Summary

A CronCreate task created with durable: true in a session fires into a different, later session on the same pid after /clear. The receiving session has no way to tell the incoming prompt was scheduled by a prior, now-cleared conversation - it appears in the transcript indistinguishable from a real user message. The docs state that "starting a fresh conversation clears all session-scoped tasks"; durable: true is not documented in the scheduled-tasks reference page.

What I verified

All facts below are verified by inspecting the actual on-disk state of my own sessions (~/.claude/projects/<project>/*.jsonl) and the running processes (ps, $PPID walk).

  • Same pid, different sessions across /clear: my current process (pid 83392, CLI claude -c) has two session transcript files in this project:
  • e357e60d-...jsonl - an earlier session, last-modified ~8 hours before the fire time
  • 87b3daad-...jsonl - the current post-/clear session
  • The prompt was scheduled in e357e60d, via a CronCreate tool call whose exact input was:

``json
{
"cron": "0 1 18 4 *",
"recurring": false,
"durable": true,
"prompt": "…time to rebuild the ... machine …\n\nWorking directory: /Users/arivkin/GitHub/<project>\n\nSteps (execute autonomously, no confirmation needed): …\n4. <destructive shell command with irreversible impact on production state>\n…"
}
``

  • The task is recurring: false and durable: true.
  • durable is not documented in the scheduled-tasks docs.
  • /clear did not cancel it. The docs say "Starting a fresh conversation clears all session-scoped tasks." The task survived the clear and fired at its scheduled time into the post-clear session (87b3daad), despite that session never having called CronCreate (confirmed: CronList in the live session returns "No scheduled jobs").
  • The prompt delivered into 87b3daad matches byte-for-byte the prompt field of the e357e60d CronCreate call (confirmed via grep on both files).
  • The other terminal's session file (984981fb-...) is unrelated - the leaked prompt text does not appear anywhere in it. This is not a cross-terminal leak.
  • Docs (scheduled-tasks) state:

> "Tasks are session-scoped: they live in the current conversation and stop when you start a new one."
> "Starting a fresh conversation clears all session-scoped tasks."

Security impact

The scheduled prompt instructed step-by-step destructive actions framed as "execute autonomously, no confirmation needed", including a shell command with irreversible impact on production state. Because the prompt arrives after /clear with no origin marker, the receiving assistant has no way to distinguish it from a real user message. A less suspicious agent would have executed it. A user's own prior Claude session can effectively prompt-inject its successor across /clear.

Reproduction

  1. Start a session: cd <project>/ && claude.
  2. Have Claude schedule a durable one-shot task that fires a few minutes out, with a distinctive prompt. The CronCreate tool call looks like:

``json
{ "cron": "<near-future>", "recurring": false, "durable": true, "prompt": "MARKER" }
``

  1. /clear the conversation in the same session.
  2. Wait for the scheduled time.

Expected, per docs: the task is cancelled by /clear and does not fire.
Actual: the task fires into the post-/clear conversation, which has no record of having scheduled it.

Suggested fix directions

  • If durable: true is intentionally designed to survive /clear, document it on the scheduled-tasks page and surface its semantics to the user when they /clear ("N durable tasks will still fire: …").
  • Wrap durable task firings in an origin marker the receiving model can see, e.g. <scheduled-task origin-session="<prior-sessionId>" scheduled-at="…">PROMPT</scheduled-task>, so it's clearly not a live user message.
  • If a durable task would fire into a session that has no matching origin record, offer the receiving model an explicit "reject, ask user, or run" gate rather than dropping the prompt straight into the transcript.
  • Consider whether /clear should cancel durable tasks by default, with an explicit flag to opt in to carry-over. The current behavior is surprising and documented as the opposite.

Test plan for fixers

  • [ ] Regression: schedule a CronCreate(durable=true) task, /clear, wait for fire time - confirm the task either does not fire or surfaces with an origin marker, not as a bare user message.
  • [ ] Regression: CronList in the post-/clear session should either still list the surviving durable task (with an "inherited from prior session" marker) or the task should have been cancelled. Current behavior (empty CronList and task still fires) is inconsistent.
  • [ ] Docs: update https://code.claude.com/docs/en/scheduled-tasks.md to describe durable, including when it is set, its effect across /clear and --resume, and how to cancel durable tasks.

Environment

  • OS: macOS (darwin 25.3.0)
  • Claude Code: CLI; confirm via claude --version.
  • Single pid, single process, single terminal. (Earlier I suspected a cross-terminal leak; verification of the on-disk session files disproved that.)

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗