Skip to content

cron: v2026.4.2 silently drops jobs.json (plain-array format) and first add clobbers all data #60799

@slideshow-dingo

Description

@slideshow-dingo

Bug type

Regression (worked before, now fails) / Data Loss

OpenClaw version

OpenClaw 2026.4.2 (d74a122)

OS and install method

  • Linux 6.8.0-106-generic (x64),
  • Node.js v24.14.1,
  • npm global (~/.npm-global/lib/node_modules/openclaw),
  • systemd user service (systemctl --user).

Summary

After upgrading to 2026.4.2, the gateway silently reports jobCount: 0 at startup despite ~/.openclaw/cron/jobs.json containing 37 valid, working cron jobs. The jobs are valid JSON, parse correctly, and were fully operational on the prior version. On first cron add or job write, only the new job is persisted — all 37 existing jobs are permanently lost.

Steps to reproduce

  1. Run a working OpenClaw instance on 2026.3.x with persisted cron jobs in ~/.openclaw/cron/jobs.json (plain JSON array format: [{job}, {job}, ...]).
  2. Upgrade to 2026.4.2.
  3. Restart the gateway.
  4. Observe cron list returns 0 jobs.
  5. Run cron add to create a test job.
  6. Observe the file is rewritten with only the new job — all previous jobs are gone.

Expected behavior

  • The loader reads existing valid JSON arrays and loads all jobs, or
  • If a format migration is required, it migrates the old format and warns, or
  • At minimum, the loader logs an ERROR explaining why it rejects the file.

Actual behavior

Gateway logs show exactly:

{"module":"cron","storePath":"/home/moltbot/.openclaw/cron/jobs.json"}
{"jobCount":0,"enabledCount":0,"withNextRun":0}
"cron: armTimer skipped - no jobs with nextRunAtMs"
"cron: started"

Zero jobCount, zero warnings, zero errors. The file is valid JSON but silently skipped. The first subsequent write clobbers the file: it creates a new { "version": 1, "jobs": [...] } envelope containing only the newly-added job, destroying all pre-existing entries.

Logs and evidence

Gateway startup logjobCount: 0 despite valid file:

{"module":"cron","storePath":"/home/moltbot/.openclaw/cron/jobs.json"}
{"jobCount":0,"enabledCount":0,"withNextRun":0}
"cron: armTimer skipped - no jobs with nextRunAtMs"
"cron: started"

Before write — file is valid plain array with 37 items:

$ python3 -c "import json; jobs=json.load(open(\"/home/moltbot/.openclaw/cron/jobs.json\")); print(len(jobs))"
37
$ python3 -c "import json; jobs=json.load(open(\"/home/moltbot/.openclaw/cron/jobs.json\")); print(jobs[0].keys())"
["id","agentId","sessionKey","name","enabled","createdAtMs","updatedAtMs","schedule","sessionTarget","wakeMode","payload","delivery","state","failureAlert","description"]

After cron add — file overwritten with single job in new versioned format:

{
  "version": 1,
  "jobs": [{ /* only the newly-added test job */ }]
}

The add call itself succeeds (returns a valid job ID), but the write path ignores the pre-existing 37 jobs and replaces them.

Root cause analysis

The 2026.4.2 cron loader appears to have changed the store format from a plain JSON array ([{job}, ...]) to a versioned envelope ({ "version": 1, "jobs": [{job}, ...] }). The loader:

  1. Does not recognize or migrate the old plain-array format.
  2. Does not log an error or warning when it fails to parse the file.
  3. Reports jobCount: 0 silently, making operators believe there were never jobs.
  4. Writes the new versioned format on first mutation, silently clobbering all old data.

Impact

  • Data loss is automatic and irreversible: the first write after startup permanently destroys all pre-migration jobs. With 37 jobs in my case, all scheduling, delivery config, failure alerts, and job history are lost.
  • openclaw doctor --fix does not detect or migrate the issue.
  • This is the third distinct report of cron jobs being silently lost (see Related issues).

Related issues

  • #52569 — "Cron jobs silently lost after upgrading 2026.3.12 → 2026.3.13" (same symptom, older version)
  • #53746 — "saveCronStore overwrites jobs.json from partial in-memory state after restart" (same destructive-write pattern)
  • #60334 — "2026.4.2 update rejects legacy config keys and cron jobs.json may require manual backup restore" (same version, though reporter had malformed JSON)
  • #33451 — "legacy cron jobs with string schedule + top-level command are not migrated on load" (related migration gap)

None of the above have been resolved. This report adds the specific v2026.4.2 format-change migration gap with full evidence and repro.

Proposed fix

  1. Backward-compatible loader: The cron store loader should detect both formats (plain array and versioned envelope) and load them transparently.
  2. Graceful migration: If the file is a plain array, wrap it in { "version": 1, "jobs": [...] } on the next write.
  3. Fail-safe write path: Use a read-merge-write pattern instead of writing-only-in-memory-state (see Bug: saveCronStore overwrites jobs.json from partial in-memory state after restart, causing silent job loss #53746 for details).
  4. Explicit error logging: If the file cannot be parsed, log a WARN or ERROR with the first 200 characters so operators can diagnose instead of seeing silent zero-count failures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Emergency: data loss, security bypass, crash loop, or unusable core runtime.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:data-lossCan lose, corrupt, or silently drop user/session/config data.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions