Running scheduled coding agents safely
Published 2026-05-22 by Owner
A scheduled agent runs with no human at the trigger. Whatever it does, it does before you see it, which removes every control that depends on your presence. The controls that remain have to be structural: limit what the agent can reach, force its output through a review gate, cap what it can spend, and keep a way to stop it. These apply to any unattended runner — Anthropic Routines (scheduled, API, or GitHub-event triggers, launched April 14 as a research preview), Antigravity 2.0’s background tasks, or a plain cron job.
The principle behind all of them is one rule: an unattended agent may propose work but never finalize it. Each step below enforces that rule at a different layer — the repository, the review, the run, and the off switch — so that a single failure at any one layer is caught by the next.
Scope before you schedule
Give a scheduled agent the narrowest reach that lets it do its one job. Give it its own machine account with a token scoped to a single repository, not your personal credentials, so a compromised or confused routine cannot reach across everything you can. Restrict it to a working branch it is allowed to push, never a protected one. Protect the default branch so an unattended agent physically cannot merge its own work:
gh api -X PUT repos/OWNER/REPO/branches/main/protection --input - <<'JSON'
{
"required_pull_request_reviews": { "required_approving_review_count": 1 },
"enforce_admins": true,
"required_status_checks": null,
"restrictions": null
}
JSON
The protection endpoint needs all four keys present, so the ones you are not constraining are sent as null. With main protected this way, the agent can only open a pull request. A human still has to merge it.
Force a review gate
Encode the human reviewer so the agent cannot route around it. A CODEOWNERS file under .github/ requires a named reviewer on the paths the agent is allowed to touch:
/src/ @your-team/reviewers
/infra/ @your-team/platform
Combined with the branch protection above, a scheduled agent’s PR cannot merge until a code owner approves it.
Bound the blast radius
A schedule that misfires does not misfire once. A cron expression with a bug, or a webhook that retries, can start the agent dozens of times before anyone notices, and each run spends tokens and opens PRs. If the agent runs in CI, cap concurrency so overlapping triggers collapse into one run, and give every run a hard timeout so a stuck agent stops on its own rather than billing until you wake up:
concurrency:
group: scheduled-agent
cancel-in-progress: true
jobs:
agent:
timeout-minutes: 20
The concurrency group is what turns “fired twelve times” into “ran once”; the timeout is what bounds the cost of any single run that goes wrong.
Dry-run first
Before letting a new scheduled job act, run it for a few cycles in a mode that produces a patch artifact instead of a PR, and read what it would have changed. Diff against HEAD rather than staging everything, so stray workspace files never land in the patch:
git diff HEAD > scheduled-agent.patch
Upload that patch as a CI artifact, review it across several runs, and only enable PR creation once the proposed diffs are consistently safe.
Keep a kill switch
When a scheduled job misbehaves you need to stop it now, not after the next fire. For a GitHub Actions schedule:
gh workflow disable scheduled-agent.yml
gh run list --workflow scheduled-agent.yml --limit 5
gh run cancel <run-id>
For Routines or Antigravity tasks, disable the routine or task in its dashboard — treat “where is the off switch” as a setup question you answer before the first run, not an incident question you answer during one.
Audit what ran
An unattended agent needs an after-the-fact record. Keep its runs reviewable on a cadence:
gh run list --workflow scheduled-agent.yml --json databaseId,conclusion,createdAt
Weekly is enough for a low-frequency job. The approval-risk framing for autonomous runs is in AI coding security modes; least-privilege scoping for the agent itself is in Claude Code permission modes. The argument for why any of this matters is the agent that runs while you sleep.