Scheduled AI Agent Failures: What Breaks Differently From a Cron Job

· 6 min read

We’ve argued that a scheduled agent run has the same operational shape as any other cron job: it needs a trigger, a place to run, a record of what happened, and a safe way to deploy changes. That’s true, and it’s also incomplete. A shell script either exits 0 or it doesn’t. An agent can exit 0, produce a plausible-looking result, and still have done the wrong thing — which means the usual runbook catches maybe half of what actually goes wrong once the thing running on your schedule is an LLM loop instead of pg_dump.

Here’s the other half — the failure modes that are specific to putting an agent behind a timer, roughly in the order to check them.

1. It exited 0 and did nothing useful

The classic cron failure is silence: the job didn’t run, or it errored loudly enough that something noticed. The classic scheduled-agent failure is worse — it ran, it returned a 200 or exit code 0, and the output is confidently wrong. An agent that was supposed to reconcile yesterday’s transactions can decide there was nothing to reconcile, format that as a clean success, and exit fine. Nothing in a standard cron monitor distinguishes that from an actual clean run, because from the trigger’s perspective, it was one.

Check: does your run record capture the agent’s actual output, not just its exit code? A pass/fail heartbeat is not enough for a job whose failure mode is “succeeded at the wrong thing.”

2. The prompt drifted, the schedule didn’t

Shell scripts don’t drift on their own between runs. Agents do, indirectly — a model version changes, a tool it calls updates its API, a system prompt gets tweaked by someone who didn’t know it was wired to a 2am cron entry. The schedule fires exactly as configured; what happens once it fires quietly stops matching what you tested three weeks ago.

Check: is there a diff-able record of what changed about the agent’s inputs (prompt, tools, model) between the last known-good run and now? If the answer lives in a chat history and a deploy log, correlating them after the fact when something breaks is a bad night.

3. It ran twice, or ran into itself

Ordinary idempotency problems get worse with agents because a second concurrent run doesn’t just duplicate a database write — it can duplicate an action with a side effect the agent decided to take: a second email sent, a second API call to a paid service, a second Slack message posted. Cron’s classic double-run causes (a job that overruns its own interval, a missed-then-caught-up trigger after a reboot) hit an agent exactly the same way they hit a shell script, but the blast radius is bigger because the thing running twice has judgment, not just a fixed set of steps.

Check: does the job itself lock against overlap (a lockfile, a flock, a check against its own last-run record), or is the schedule alone assumed to guarantee non-overlap? Assumed is not guaranteed — that assumption breaks the first time a run legitimately takes longer than the interval.

4. The failure is a cost, not a crash

A stuck shell script wastes CPU time you already paid for. A stuck or looping agent run burns tokens on every retry, every re-plan, every tool call it makes while confused — and it can do that for a while before anything looks like a conventional “failure.” A job that used to cost cents can, after a tool it depends on starts returning malformed responses, cost real money before a human notices the pattern.

Check: is there a ceiling on what a single scheduled run can spend — a token budget, a max tool-call count, a wall-clock timeout — enforced by something other than the agent’s own judgment about when to stop?

5. It needed a machine-specific credential that isn’t there anymore

This one’s not agent-specific, but it bites agent jobs harder because agents tend to touch more things — more APIs, more keys, more scopes — than a single-purpose script does. A key rotates, a .env gets regenerated on redeploy, a credential that lived in one shell’s environment doesn’t exist in the one cron actually runs in. The run doesn’t fail with “auth error” as often as you’d hope; sometimes it fails with the agent working around the missing capability and doing something adjacent instead.

Check: run the job’s actual scheduled command by hand, in the same shell cron would use — not your interactive shell, which has a different, more complete environment — the same way you’d debug a silently-failing shell script. The usual runbook already covers this; it’s not new, it’s just more consequential when the thing missing a credential is empowered to improvise.

What this changes about how you schedule the job

None of the above argues for a different trigger mechanism. It argues for treating the output, not just the exit code, as the thing your scheduling layer needs to keep a record of — and for a hard ceiling on cost and concurrency that doesn’t rely on the agent noticing its own failure. norc’s job history already stores stdout/stderr and run duration for every scheduled command regardless of what it runs, which is enough to catch #1, #3, and #5 by hand; #2 and #4 are on you to instrument in the job itself, because no scheduler can see inside a model call it isn’t making.

If you’re pairing an agent to a schedule for the first time, that’s the actual checklist — not “which framework,” but “what does this job do when it’s wrong, and will anything notice.”

FAQ

Does norc detect that an agent’s output was wrong, not just that it ran? No, and no scheduling tool can — that requires domain knowledge about what “correct” looks like for your specific job. What norc gives you is the raw material to check: full output capture per run, so “wrong but exit-0” is something you can actually see in the history instead of inferring from downstream damage.

Should I set a timeout on every scheduled agent job? Yes, if the tool you’re using it through will actually enforce one — cron itself won’t kill a hung process for you. Wrap the command with timeout (or your agent framework’s own limit) so a hung run doesn’t sit there past the next scheduled trigger.

Is overlap-locking something norc handles, or the job? The job. norc runs the command you give it on schedule; if that command needs to refuse to start a second copy of itself, that’s a flock or equivalent inside the script, the same as it would be for any long-running cron job.


Scheduling the agent was the easy part. norc gives every scheduled run — agent or otherwise — a real history of what actually happened, on whatever machine it ran on, so “it exited fine” and “it did the right thing” stop being the same question by default.