Legacy .claude/scheduled_tasks.json is loaded by the VS Code runtime, enabled: false is ignored, jobs fire as user prompts, and the file is rewritten on disk

Status Open
Reported on v2.1.263
Maintainer reply None cached
Activity 0 comments · opened Sep 11, 2026

Title

Legacy .claude/scheduled_tasks.json is loaded by the VS Code runtime, enabled: false is ignored, jobs fire as user prompts, and the file is rewritten on disk

Environment

  • OS: Windows 11 Home 10.0.26200
  • Claude Code CLI: 2.1.263
  • Runtime that produced the behavior: VS Code extension sessions whose transcripts report version: 2.1.266 and 2.1.267 (entrypoint: claude-vscode)
  • Project-scoped file: <project>/.claude/scheduled_tasks.json, created 2026-04 by an earlier Claude Code build, schema:
{
  "tasks": [
    {
      "id": "…",
      "cron": "0 8 * * *",
      "prompt": "…",
      "createdAt": 1776304137931,
      "lastFiredAt": 1776727800438,
      "recurring": true,
      "enabled": false
    }
  ]
}

All five entries had "enabled": false and had been disabled since 2026-04. The file was committed in that state.

What happened

  1. With a CLI session (2.1.263) open in the project, the VS Code extension runtime spawned two short-lived sessions (14–18 transcript lines, zero assistant turns, zero tool calls, entrypoint: claude-vscode).
  2. Each spawn coincided, to the second, with a rewrite of <project>/.claude/scheduled_tasks.json. The file went from 5 tasks → 2 tasks (the enabled key was dropped from the survivors) → {"tasks": []}. The final file has a fresh CreationTime equal to its LastWriteTime, i.e. it was created anew and moved into place rather than edited.
  3. At the same moments, the prompt strings of the disabled tasks were enqueued into the open CLI session as user messages. In the transcript those records carry isMeta: true, promptSource: "sdk", queueSkipAttachments: true, and no origin field, whereas genuine typed messages carry origin: {"kind": "human"}. From the model's point of view they are indistinguishable from the user typing the prompt unless it inspects the transcript.
  4. No tool call in any transcript on the machine wrote the file; the rewrite came from the runtime itself.

The fire times did not match the cron expressions (they were minutes apart in the evening, for jobs scheduled at 08:00 daily, hourly at :05, Mondays 11:00 and the 20th at 14:00). The pattern is consistent with "tasks older than 7 days fire one final time and are deleted" being applied on load to stale, disabled entries.

Expected

  • enabled: false entries are never fired, or, if the field is no longer part of the schema, unknown/legacy files are ignored with a warning instead of being treated as live.
  • The CronCreate tool description says: "Jobs live only in this Claude session — nothing is written to disk" and durable: Has no effect — durable persistence is not available. Either the description or the runtime should change; today the runtime both reads and rewrites a project file.
  • A prompt injected by the scheduler should not be presented to the model as an ordinary user turn without a visible marker. isMeta / promptSource: "sdk" are only in the on-disk transcript.

Impact

The stale prompts included instructions to run a repository script and to generate reports. Nothing executed because the model in the CLI session stopped and asked for confirmation, and the spawned VS Code sessions never produced an assistant turn. In a permissive permission mode the same injection would have run unattended, against the user's explicit (and version-controlled) enabled: false.

Repro (as observed)

  1. Have a project with the legacy .claude/scheduled_tasks.json above (all tasks enabled: false, createdAt older than 7 days).
  2. Open the project in VS Code with the Claude Code extension (2.1.266+) while a CLI session is also open in the same directory.
  3. Watch the file: it is rewritten and emptied; watch the CLI session: the task prompts arrive as user messages.

Suggested fixes

  • Honour enabled: false, or refuse to load files whose schema is not the current one.
  • Make the CronCreate / durable-persistence description match the runtime, and document where the runtime reads and writes.
  • Surface scheduler-originated prompts to the model with an explicit marker (or via the system/reminder channel) so they are not mistaken for human input.
  • Do not rewrite a version-controlled file in place on load; at minimum, log the rewrite.

View original on GitHub ↗