Cron Semaphore — OpenClaw Plugin
Prevents concurrent cron job execution in OpenClaw. When two cron jobs are scheduled at the same time, one runs and the others wait their turn — no more session corruption errors from jobs stepping on each other.
Why
OpenClaw fires each cron job independently. If two jobs land on the same
minute (e.g. 0 18 * * 0), both spawn isolated agent runs simultaneously.
Those runs share the same agent runtime, and their embedded sessions can
collide — producing EmbeddedAttemptSessionTakeoverError or mysterious timeouts
as they fight over session files.
This plugin serializes those runs. Only one cron job runs at a time. The rest are cleanly skipped with a message and retry on their next scheduled interval.
How it works
- A cron job starts →
before_agent_runhook fires. - Plugin checks a lock file in the workspace directory.
- No lock? Acquire it and let the job through.
- Lock exists from another job? Block this run with a clean message.
- Job finishes →
agent_endhook fires → lock is released. - Stale locks (crash recovery) are auto-cleared after 10 minutes or on Gateway restart.
Install
openclaw plugins install clawhub:cron-semaphore
Or from the repo:
git clone https://github.com/GMDEEP/cron-semaphore.git
cd cron-semaphore
openclaw plugins install --link .
Configure
The plugin activates on startup. For it to intercept conversation hooks, you must enable conversation access in your OpenClaw config:
{
plugins: {
entries: {
"cron-semaphore": {
config: {
// (optional) Override stale lock timeout in ms. Default: 600000 (10 min)
staleTimeoutMs: 600000
},
hooks: {
// Required — the before_agent_run hook is a conversation hook
allowConversationAccess: true
}
}
}
}
}
Config options
| Option | Type | Default | Description |
|---|---|---|---|
staleTimeoutMs | integer | 600000 | Milliseconds before a lock is considered stale |
lockFilePath | string | auto | Custom path for lock file (defaults to workspace) |
Lock file
The plugin writes .cron-semaphore-lock.json in the workspace directory.
It contains:
{
"jobId": "a6914b45-b21c-4e5e-87d0-63fa976a04ed",
"jobName": "a6914b45...",
"acquiredAt": 1780883000000,
"lastHeartbeat": 1780883005000
}
If a job crashes without releasing the lock, it auto-expires after the stale timeout (10 minutes by default). Gateway restart also cleans stale locks.
License
MIT