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
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.266and2.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
- 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). - Each spawn coincided, to the second, with a rewrite of
<project>/.claude/scheduled_tasks.json. The file went from 5 tasks → 2 tasks (theenabledkey 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. - At the same moments, the
promptstrings of the disabled tasks were enqueued into the open CLI session as user messages. In the transcript those records carryisMeta: true,promptSource: "sdk",queueSkipAttachments: true, and nooriginfield, whereas genuine typed messages carryorigin: {"kind": "human"}. From the model's point of view they are indistinguishable from the user typing the prompt unless it inspects the transcript. - 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: falseentries 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
CronCreatetool description says: "Jobs live only in this Claude session — nothing is written to disk" anddurable: 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)
- Have a project with the legacy
.claude/scheduled_tasks.jsonabove (all tasksenabled: false,createdAtolder than 7 days). - 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.
- 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.