Skip to content

[Bug] Cron tasks marked as lost after gateway restart — activeJobIds not persisted #79196

Description

@awabuda

Bug Description

Cron tasks that are currently running are incorrectly marked as `lost` after a Gateway restart.

Root Cause

The active job tracking mechanism uses an in-memory `Set` (`activeJobIds`) that is not persisted across restarts.

Code flow:

  1. When a cron job starts running, markCronJobActive(jobId) adds the job ID to a memory-only Set
  2. Gateway restarts → the Set is cleared
  3. After the sweeper grace period (5 minutes), isCronJobActive() returns `false`
  4. Task is marked as `lost` with error: "backing session missing"

Key files:

  • `task-registry.maintenance-D1dTWf8y.js` — `CRON_ACTIVE_JOB_STATE_KEY` singleton stores only in-memory `activeJobIds` Set
  • `server.impl-BbJvXoPb.js` — calls `markCronJobActive`/clearCronJobActive but state is never persisted

Impact

  • Any long-running cron task (>5 min) will be marked `lost` if Gateway restarts during execution
  • `cron restart gateway` workflow breaks running jobs
  • Config changes requiring Gateway restart disrupt in-flight tasks

Proposed Fix

Persist `activeJobIds` to the cron store file (`~/.openclaw/cron/jobs.json`).

Implementation approach:

  1. Store shape change — add `activeJobIds: string[]` field to the cron store JSON:
    ```json
    {
    "version": 1,
    "jobs": [...],
    "activeJobIds": ["job-id-1", "job-id-2"]
    }
    ```

  2. In `server.impl-BbJvXoPb.js`:

    • `markCronJobActive(jobId)` → push to `state.store.activeJobIds` + `persist(state)`
    • `clearCronJobActive(jobId)` → filter from `state.store.activeJobIds` + `persist(state)`
    • `ensureLoaded()` → on startup, for any job with `job.state.runningAtMs` set, call `markCronJobActive(job.id)` to rebuild the in-memory Set
  3. In `store-0nH_zmSJ.js`:

    • `loadCronStore()` → load and hydrate `activeJobIds` from file
    • `saveCronStore()` → persist `activeJobIds` alongside jobs

This approach leverages the existing `persist()` mechanism and the already-persisted `job.state.runningAtMs` field to determine which jobs were running at restart time.

Workaround

None — this is a design gap in the persistence layer.

Environment

  • OpenClaw version: 2026.4.2+
  • Gateway running as launchd/systemd service
  • Any cron job with execution time > 5 minutes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions