Scheduling a coding agent takes a few minutes. A launchd plist, a cron line or a desktop scheduled task will start Claude Code or Codex at 2am. Getting something useful out of it by 8am is harder.
I've been reading what people who run agents on a timer have posted lately: a dev.to write-up of a week running as a scheduled agent, the HN thread on tmux and Claude Code, an r/LLMDevs thread asking what you actually record when an agent runs unattended, and a handful of newer posts. The same failures come up again and again, and very few of them are the model writing bad code. Most are plumbing. Here they are, grouped by what you find in the morning.
1. It never ran, or it stopped halfway
The thing that was supposed to start the run wasn't running.
- Desktop scheduled tasks only fire while the app is open on that machine, and recurring runs start a few minutes late on purpose (source).
- The laptop slept or the lid closed. The Omnara founders said their earlier remote-control setup for Claude Code and Codex "stopped when the laptop closed" (Show HN).
- The terminal went away. Several people in the tmux thread said session persistence was the reason they started using a multiplexer at all: closing a terminal by accident, or putting the laptop to sleep, shouldn't end the agent.
What helps: start runs from launchd (systemd on Linux), not from an app window. launchd has one more advantage over cron on a laptop. If the machine is asleep at the scheduled time, cron skips the job, while launchd runs it when the machine wakes. Keep the Mac awake for the window you need (caffeinate -i covers idle sleep, not a closed lid on battery), and run the session somewhere you can reattach to.
2. It ran on time and did nothing
From the scheduler's side this one looks fine.
A post from this week has a clean example. A launchd job fired at 21:00 two nights in a row and exited one second later. The runbook was handed to the agent CLI as an argument, it started with YAML frontmatter, and the CLI parsed --- as an unknown option and quit with exit code 1. The fix was putting -- before the prompt so everything after it is read as text. The exit status had been sitting in launchctl list since the first night, and nobody checked it.
Two more versions of the same thing:
- In a nightly loop that turns GitHub issues into PRs, one-shot sessions that started a build in the background and waited to be told it finished just ended with nothing pushed, because nothing was going to wake them. The loop's prompt now says never to background anything and to commit as it goes.
- A nightly job finds no data, writes a CSV with only a header row, reports that "no orders were found" and exits 0 (example). Every process-level signal is green.
What helps: check the output itself. Does the file exist, is it from tonight, does it have anything in it. The exit code and the agent's last message can both look fine when it doesn't.
3. It waited all night for someone who wasn't there
A permission prompt at 2am means the run does nothing until you wake up. Same for any step that needs a human yes, like publishing something public. The scheduled-agent post adds browser sign-ins: one lasted about a day, the next run hit a login page, and an agent shouldn't be typing passwords.
What helps:
- Allow the tools a brief needs before you schedule it. The issue loop runs with an explicit allow/deny list, and merging, deploying and force-pushing are on the deny list, so the prompt isn't the only thing stopping them.
- Write narrow permissions into the brief itself: which accounts, which kinds of action.
- When a run needs a sign-in that has expired, have it draft and queue the work for you.
- A desktop notification only reaches someone sitting at that machine. Put what the agent needs from you in a file you read in the morning.
4. It did something you didn't want
This post is worth reading before your first overnight run. The author had Claude Code working on an iOS app overnight in auto mode, where a classifier answers the permission prompts. A subagent asked to add a home-directory guard to a script that runs rm -rf ran the test before writing the guard, the test was given the real home directory, and about 9 days of unpushed commits went with the local .git. Their explanation: the permission checks and their own hooks look at the one command line Claude types, and they can't see a path passed to rm -rf inside a script that command runs. Their main defenses now are the sandbox (sandbox.enabled: true and allowUnsandboxedCommands: false in settings), a backup off the Mac, and pushing commits soon after approving them.
Smaller collisions show up too. In the issue loop, a batch of refactor PRs collided on CLAUDE.md and README.md, so each issue now gets its own git worktree and can be held behind another one with an After #N line. And an Ask HN from this week makes the point that the risk grows when a scheduled agent has MCP servers and credentials on the machine.
5. It used up the week
On a subscription, a bad night costs you the next day. In the agent-usage-guard Show HN, a commenter who had burned a whole usage window in minutes more than once wrote a hook that stops subagents from spawning past 80% of the window. They report their biggest burst went from 365 spawned agents to 27.
The issue loop reads the same numbers /usage shows before every issue and ends the night when tonight has used 10 points of the week, the week is past 70%, or the 5-hour window is past 80%. If it can't read the numbers it assumes 100% and stops. The one weekend its author raised the night allowance to 60 points, the week went from 20% to 48%.
6. It worked, and nobody could tell
A run that went fine but left nothing to read still costs you a morning, because you end up opening every session to find out what happened. In the tmux thread one commenter called the sessions "an implementation detail" they only want to jump into "when something is wrong". Another built their own setup mainly to see which sessions are still running, which need help and which are done.
The cheapest fix is a few lines at the end of every brief:
When you finish, write a short report to reports/<date>.md:
what you changed, which files, anything you assumed,
and what you need from me.
In the morning you read the reports and only open a transcript when a report says something went wrong. The scheduled-agent post also describes a loop to watch for: when nothing outside changes, each fresh session reads the plan, finds nothing new and writes the same plan for tomorrow. A decision date written into the plan ("if X still isn't live by day 7, do Y") ends it.
Before the first unattended night
- Runs start from launchd or systemd, not an open app or terminal
- The machine stays awake for the window, and sessions survive a closed terminal
- The prompt can't be mistaken for a flag
- The allow list covers what the brief needs; merge, deploy and force push are denied
- Sandbox on, a backup off the machine, commits pushed
- One worktree per agent
- A usage check before each run that stops when it can't read the numbers
- An output check after each run
- A written report per run, read in the morning
What I built for it
I wanted to run agents locally off-hours on the Claude subscription I already pay for, so I built a Mac app for it, Overboard. Each scheduled brief (any prompt or plugin slash command: a security review, a blog post, social discovery) runs as a real Claude Code session in its project folder that you can watch or take over. There's a keep-awake menu so the Mac doesn't sleep while agents need it, and each department has a captain that reviews the last runs and only asks me when it's stuck. Sessions run in auto mode, so section 4 applies to it as much as anything else: the sandbox, the deny list and the backup are still yours to set up. The Mac has to be awake and logged in.
Top comments (0)