← all posts

why your AI agent's scheduled task stopped running

A dark empty office at night, a laptop with a black screen on the desk and a wall clock above it

Your schedule is probably fine. The process that was supposed to fire it isn’t running. That’s what almost every “my AI agent’s scheduled task stopped working” case turns out to be: the agent’s built-in scheduler lives inside the agent’s own process, and when that process dies at logout, reboot or crash, the jobs stay listed and nothing runs. No error. No email. Just silence.

why does the agent’s own scheduler die?

Because it’s a tenant, not a landlord.

OpenClaw is the clean example. Its cron engine runs inside the Gateway process, not inside the model. The jobs themselves persist — they live in OpenClaw’s SQLite state database now, and openclaw doctor --fix imports the legacy ~/.openclaw/cron/jobs.json, jobs-state.json and runs/*.jsonl into it. So openclaw cron list cheerfully shows you a full schedule while exactly zero of it executes.

Persistence and execution are different promises. Only one of them survives you closing the terminal. Anything scheduled while the Gateway is down is simply missed — there’s no external timer holding the rope.

Hosted task features (the ones that run on the vendor’s servers) don’t have this problem. Anything you host yourself does.

the agent is running and the job still doesn’t fire

Four causes, in the order worth checking:

  1. You’re looking at the wrong instance. The scheduler runs in the gateway service, not the CLI process you created the job from. openclaw gateway status — confirm the live gateway is the one that loaded your config.
  2. The config was never reloaded. Schedules load at service start. Edit the config without restarting and the running scheduler keeps its old list forever. openclaw gateway restart.
  3. The job is half-defined. Jobs created through chat sometimes land without enabled: true, or with a delivery target aimed at a session or channel nobody reads.
  4. It fired and you missed it. Firing and delivering fail separately. openclaw cron runs --id <jobId> --limit 50 tells you which of the two you’re dealing with.

That last distinction saves the most time. Half the “it never ran” reports are “it ran into a void.”

it works when I run it manually but not on a schedule

Manual execution bypasses the trigger. That’s the whole answer, and it bites in two different tools.

In n8n, clicking Execute workflow runs it from the canvas without making the trigger live. The workflow has to be published, and editing a published workflow doesn’t push the change live on its own — save, then publish again. The toggle used to be labelled “Active” and is now a Publish button, which is why the guide you’re following doesn’t match your screen.

In plain cron, it’s the environment. Your shell loads .bashrc and .profile; cron hands your job a bare-minimum environment with none of it. If your agent runs on Node installed through nvm, cron has never heard of node. Use absolute paths, or source nvm inside a wrapper script. To see what cron actually gives you, schedule this for a minute from now and then read the file:

* * * * * env > /tmp/cron-env.txt

does my agent survive me logging out?

Not if you’re running it as a systemd user service. By default systemd-logind stops your user manager shortly after your last session ends, and every user service goes with it. Your agent dies when you close SSH, and the schedule dies with it.

sudo loginctl enable-linger $USER
loginctl show-user $USER --property=Linger

You want Linger=yes. Without it, tonight’s disconnect kills it again.

tmux and nohup survive a logout but not a reboot. A system-level systemd unit survives both, which is why running the agent as a proper service is the fix that makes the other four checks unnecessary.

it runs, just at the wrong time

Timestamps without a timezone are treated as UTC. That alone explains a lot of “missed” 9am jobs.

OpenClaw also staggers recurring top-of-hour jobs by up to 5 minutes on purpose, to avoid load spikes. If you need it on the exact minute, pass --exact, or set your own window with --stagger 30s.

how to stop it happening again

  • Put the scheduler inside something that restarts itself: a system-level systemd unit, or a container with restart: always.
  • If it has to be a user service, enable lingering before you need it.
  • Add one job whose only purpose is to ping you daily. Silence is the only symptom this failure has, so you need something whose absence is loud.
  • Wire failure alerts to the unit itself (OnFailure= in systemd), not to the agent. An agent that isn’t running can’t tell you it isn’t running.

A scheduled task that fails loudly is a bug. One that fails silently is a bug you discover three weeks later, when somebody asks why the reports stopped.