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:
- When a cron job starts running,
markCronJobActive(jobId) adds the job ID to a memory-only Set
- Gateway restarts → the
Set is cleared
- After the sweeper grace period (5 minutes),
isCronJobActive() returns `false`
- 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:
-
Store shape change — add `activeJobIds: string[]` field to the cron store JSON:
```json
{
"version": 1,
"jobs": [...],
"activeJobIds": ["job-id-1", "job-id-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
-
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
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:
markCronJobActive(jobId)adds the job ID to a memory-onlySetSetis clearedisCronJobActive()returns `false`Key files:
clearCronJobActivebut state is never persistedImpact
Proposed Fix
Persist `activeJobIds` to the cron store file (`~/.openclaw/cron/jobs.json`).
Implementation approach:
Store shape change — add `activeJobIds: string[]` field to the cron store JSON:
```json
{
"version": 1,
"jobs": [...],
"activeJobIds": ["job-id-1", "job-id-2"]
}
```
In `server.impl-BbJvXoPb.js`:
In `store-0nH_zmSJ.js`:
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