smithfam.ai · how-to · 26 Jul 2026
I ran five scheduled Claude Code tasks against a live site overnight on 25 and 26 July 2026, staggered about ninety minutes apart, while I slept. This is what worked, what I got wrong on the first attempt, and why the best result of the night was a bug I told it not to fix.
I build pointsandprompts.com out of a private repo, mostly with Claude Code and a Cowork session that plans and reviews. Scheduled tasks are how the two of them keep working after I go to bed: a task fires on a timer, reads a plan file, runs a wave of work, and writes up what happened. Getting five of those to fire in sequence without anything colliding took fixing two mistakes I'd already made once — on other tasks, a few days earlier.
I built the plan with cronExpression the first time, because that's the
field I already knew from other tasks on the account. A cron trigger keeps its
schedule after it fires, so a task set up for "run tonight at 11" runs again the next
night, and the one after that, redoing work that already shipped. fireAt
is the one-shot version: it fires once and disables itself. I had to go back through
the plan and convert every task from one field to the other before wave one went
out.
Registry fields, and what happens if you skip them
| Field | Where it lives | What goes wrong unset |
|---|---|---|
| fireAt | the task's trigger | cronExpression reruns the task on the same schedule forever |
| model | scheduler UI (the JSON gets rewritten) | inherits the app default; not a SKILL.md frontmatter field |
| permissionMode | scheduled-tasks.json entry | the first real run stalls on a 3am approval prompt |
I wrote up this one in more depth on The model nobody
set, with the actual dollar numbers from auditing my own Claude usage. Read that
if you want the cost side of the argument. The short version for tonight's plan: a
task with no model set doesn't get a sensible default, it inherits Opus, and the fix
lives on the task's entry in scheduled-tasks.json, the registry the
scheduler actually reads, nowhere near the skill file. Writing
model: claude-sonnet-5 into a SKILL.md frontmatter block
changes nothing and looks enough like a fix that I didn't think to check further the
first time I hit it.
Correction, written the same night. Editing that JSON directly does not hold. I set 17 tasks that way, verified them by re-reading the file, and found 25 of 29 unset again twenty minutes later. A second session on the same machine hit it twice and watched one task come back carrying a model value nobody had written. The app holds the registry in memory and rewrites it, so external edits to the active session's registry are discarded. Set the model in the scheduler UI and treat the file as read-only. And note what my verification was worth: an immediate re-read passes every time, because it runs before the app writes back. Write, wait, re-read.
I'd been going into the task UI and hand-toggling "skip approvals" on anything I
wanted to run unattended, one click per task, every time I edited one. It's the same
kind of field as the model: permissionMode: "bypassPermissions" on the
registry entry — though see the correction above: set it in the UI, because file edits get rewritten. The part worth holding onto is the limit: I
granted it to the waves that read the app and report back, and withheld it from
anything that edits a live file, deploys, sends mail or posts somewhere public. Those
keep a human gate on purpose. One more thing worth doing before bed: click "Run now"
once on each task. That banks the tool approvals, so the real run at 3am doesn't stall
on a permission prompt with nobody there to answer it.
Scheduled tasks only run while the app is open. If the Mac sleeps, the tasks queue and fire whenever it wakes next, and "overnight work" quietly turns into "morning work" instead. Check the sleep settings before you set five waves you plan to wake up to, not after.
I staggered the five tasks about ninety minutes apart so no two of them could touch the same files at once. Each one got the same instruction: read one shared plan file first, rather than carrying its own copy of the rules. That mattered more than the stagger itself. Wave one found something the later waves needed before they started: six defects, one of them sitting in a data-reconcile path I didn't want touched unsupervised. Updating that single file told every wave that hadn't fired yet what was now off-limits, without me editing five separate task prompts by hand.
Each run starts with no memory of the planning conversation, so the plan file has to say plainly which repo, which account, and what not to touch. None of that carries over on its own.
Ship what passes, stage what doesn't. A green verification at every breakpoint gets committed and deployed. Anything ambiguous or unverifiable stays on a branch with a written explanation. Never merge something you couldn't verify.
No completion claim without pasted evidence. A remembered result isn't a result. The report carries the actual command output, not a summary of it.
Nothing irreversible while nobody's watching. No destructive git, no deleting accounts or deploys, no spending, no accepting legal terms.
When it's unsure, tell it to stop. The best outcome of the night wasn't a fix. It was a refusal, twice: it wouldn't delete a test account that turned out not to be disposable, and it wouldn't measure a hidden container and report a clean pass.
Report the alarming thing first. The morning summary should lead with whatever's wrong, not bury it under what went fine.
Wave one was a verification sweep against the app's sandbox integration, and it found six defects. One was high severity: a second device could silently revert a category correction, because a stale local cache got written back to the server with a fresher timestamp and won every reconcile after that. The wave shipped none of the fixes. It restored the test account to its pre-run state and wrote up the shape of the fix for daylight, because the highest-severity finding sat in the one code path where a wrong 5am edit could lose a real user's data.
It also caught itself mid-run. An early result showed a card-matching function failing on cards it should have matched. That looked like a real bug until it noticed the failures only happened against a fake bank name it had invented for the test. Real institution names matched fine, and it flagged the false alarm in the write-up instead of letting it stand, applying the completion-claim rule above to its own work.
That's the real pitch for this. Overnight agents are good at finding things and at mechanical work with a clear pass/fail. They should stay conservative about changing things while nobody's awake to review it, and a good night can end with four waves shipped and a fifth deliberately left alone — that's not a shortfall in the run. It's what the guardrails are for.