Worked example

An hourly unattended Claude Code job and its measured cost

Reveneau's hourly unattended Claude Code job is one job on one machine: a scheduler starts it at minute 7 of every hour, it runs `claude -p` to write a short news item, builds the site, commits and pushes, and nobody reviews the item before it is published. This page describes that job from its scripts and logs, read on 4 October 2026: the chain of steps, the time caps and the three incidents that produced them, the durations it measured, the dollar figures it recorded for one step, and the cost it never measured because the main step runs with `--output-format text`. One job is one job. None of the figures here is a statistic, and the page says where each one came from.

Published October 4, 2026. Editorial.

Key takeaways

  • Reveneau's hourly job runs claude -p once per hour with --model opus pinned, acceptEdits permission mode, --output-format text and a fixed allowed tool list, by its scripts read on 4 October 2026.
  • Across 40 healthy writer passes from 10 to 12 September 2026 the shortest took 323 seconds and the longest 1,201, which is why the per-attempt cap is 1,500 seconds; across 48 runs in the same days, 41 were healthy and 7 failed on the API stopping mid-response.
  • The writer step records no token count and no dollar figure, because --output-format text returns neither; the fix is --output-format json, which Anthropic says returns total_cost_usd and a per-model breakdown.
  • The LinkedIn step, which uses --output-format json, recorded $0.78 to $2.33 per call over 6 calls on 21 September 2026, and $2.56 for the two calls of the most recent run read on 4 October 2026, at list price for the opus model.
  • No --max-budget-usd is set on either call; the job's caps are time caps, and this page recommends adding the dollar cap after the writer's cost has been measured.

Claude Code is Anthropic's coding tool: you type a request in a terminal, and an AI model reads files, runs commands and edits code for you. Everything the model reads and writes is counted in tokens, which are pieces of text. An unattended run is a claude -p call started by a script, with no person at the keyboard. The rest of this guide gives the controls for such a run from Anthropic's documentation. This page shows one real run that uses some of them and lacks others.

The run is Reveneau's own. Reveneau is an AI software development consultancy, all of its code is written by AI, and every change must pass an eval suite, a set of automated tests written from the specification, before release, so token use is a running cost of every Reveneau build. Its AI News section is written by an hourly job with nobody watching. Everything on this page about that job was read from its scripts and logs on 4 October 2026, and everything about Claude Code is from Anthropic's documentation read the same day. This page is part of the guide to the cost of agents and unattended runs in Claude Code.

One sentence before the facts. One job is one job. The durations and dollar figures below describe this job, on the days named, on one machine, with one skill. None of them is a statistic about Claude Code, about unattended runs, or about any other job, and the page uses them only to show what measuring a run looks like and where this one left a gap.

What the job is

The job is started by the operating system's scheduler, launchd, at minute 7 of every hour. It writes short news items for the AI News section of reveneau.com, builds the site, commits and pushes, and the push deploys. No person reviews an item before it is published; the site owner authorised that on 31 August 2026. The job runs in its own copy of the repository, a git worktree, and never in the checkout a person is editing.

Its steps run in a fixed order, by the scripts read on 4 October 2026:

  1. The run takes a lock, so two runs never overlap.
  2. A plain script with no model checks the news sources and writes a candidate list. Zero candidates means the run exits without starting a model.
  3. claude -p runs once with a writing skill, in acceptEdits permission mode, with --model opus pinned, --output-format text, and an allowed tool list of Read, Write, Edit, Glob, Grep, WebFetch, WebSearch and four Bash prefixes: curl, node, ls and cat.
  4. A second script fills in a LinkedIn post for each new item, one claude -p call per item, with --output-format json.
  5. A production build runs locally, and a failure stops the run.
  6. A publish script checks every changed path against an allowlist of two paths, then commits and pushes.
  7. The push is the deploy.

Each Anthropic flag in step 3 and step 4 does what Anthropic's documentation says it does, read on 4 October 2026. claude -p runs Claude Code non-interactively and exits with code 0 on success and a non-zero code on failure [1]. --model sets the model for the session with an alias such as opus or a model's full name, and overrides the model setting and ANTHROPIC_MODEL [2]; Anthropic says an alias resolves to the recommended version for the provider and updates over time, and that a full name such as claude-opus-5-5 pins one version [4]. --permission-mode acceptEdits lets Claude write files without prompting and auto-approves common filesystem commands such as mkdir, touch, mv and cp, while other shell commands and network requests still need an allowed-tools entry [1][5]. The allowed tool list is --allowedTools, also written --allowed-tools, in permission rule syntax, where a Bash rule with a trailing * allows any command starting with that prefix [1][2]. --output-format takes text, the default, json, or stream-json [1]. The page on running Claude Code from a script with a spend cap covers each flag in full.

kill and pkill are deliberately absent from the writer's allowed tools. The runner owns process lifetime, for a reason the next section gives.

The three incidents and the caps they produced

7 September 2026: the model pin. Without --model, claude -p uses whatever model the user's settings file holds; Anthropic's model configuration page lists the model field in a settings file as the fourth source in its priority order, after the flag and the ANTHROPIC_MODEL variable [4]. On 7 September 2026 that file named a model the account could not use, and six hourly runs between 02:07 and 09:07 PT failed with There's an issue with the selected model. Anthropic's error reference describes that message as the configured model name being unrecognised or the account lacking access, and gives the remedy for non-interactive mode: pass --model with a valid alias or ID, or set ANTHROPIC_MODEL [6]. The model has been pinned in the script since.

10 September 2026: the hang. The 17:07 PT run stayed open for 5 hours 47 minutes and blocked the next five hourly runs. The writer had finished its item by 17:21. Its skill had asked it to confirm the new page rendered; no server was running, so it started a development server, which never exits. It tried to stop the server with pkill, which was absent from its allowed tools, so the call was refused, and it wrote "That's OK; the process will get cleaned up" and stopped. The server had inherited the writer's output pipe (the channel that carried the writer's output to the runner), the pipe stayed open until every process holding it exited, and the run waited on a server for five hours while the finished item stayed unpublished.

Anthropic's non-interactive page, read on 4 October 2026, says a background Bash task started during a claude -p run, such as a dev server, is terminated after Claude has returned its final result and standard input has closed, with a grace period Anthropic puts at five seconds [1]. The logs read record the pipe and the wait; the Claude Code version in use that day is outside what was read, so this page makes no claim about how that rule applied. What the logs do show is that the runner was waiting on a pipe, and a pipe closes only when every process holding it has gone.

Four changes followed: the writer's output goes to a file instead of a pipe; the runner ends the writer's whole process group (the writer and every process it started) after it exits, on every path including a clean exit; a per-attempt time cap, AINEWS_WRITER_TIMEOUT_S, polled by the script because macOS ships no timeout command; and the runner starts and stops the server used to check the page (the render server) itself, on port 4479, and tells the writer never to start a server. The next run after the change: render server ready in 2 seconds, writer 7 minutes 38 seconds, 256 of 256 candidates evaluated, build OK, committed, 10 minutes 5 seconds in total. kill and pkill stayed out of the writer's tools, because the runner now prevents the problem and an unattended model with kill could end any process on the machine. Anthropic's documentation says a -p run with no permission host denies a request that would otherwise prompt [1], which is what the refused pkill call was.

12 September 2026: the retries and the emails. The attempt cap stopped the hangs and left an email every hour about something the reader could not act on. Six emails went out from the run between 22:08 on 11 September and 09:17 on 12 September 2026, against one from the watchdog, and the watchdog's was the useful one. Two changes followed. The writer now retries inside the run: up to AINEWS_WRITER_TRIES = 3 attempts, AINEWS_WRITER_BACKOFF_S = 60 seconds apart, inside a total AINEWS_WRITER_BUDGET_S = 2,700 seconds, and a retry starts only if a full attempt plus its backoff still fits the remaining budget. And a writer whose attempts all reach the cap exits quietly, with no email and no success timestamp, so the watchdog reports a real stall once. An instant-failure test the same day ran 14 attempts in 71 seconds before the attempt count and the backoff were added. A time budget alone does not bound attempts when each attempt fails in seconds. A retry also runs only against a clean worktree; if a failed attempt left files behind, the run stops instead of retrying with those files in place.

The measured durations

The caps were set from measurement, by the logs read on 4 October 2026:

  • Across 40 healthy writer passes from 10 to 12 September 2026: shortest 323 seconds, median 648, 90th percentile 952, longest 1,201. None reached 1,500 seconds, which is why 1,500 was chosen as the attempt cap, down from 1,800.
  • Across the 48 hourly runs from 10 to 12 September 2026: 41 healthy, of which 36 published an item and 5 found nothing to publish, and 7 failed. All 7 failures were the API stopping its response part way through, which nothing in the job could fix and which the next hour's run retried on its own. The page on timeouts, retries and cleanup for unattended runs gives Anthropic's description of that failure and the retries Claude Code makes before it reports one.
  • A healthy full pass took 7 to 25 minutes, measured 8 to 10 September 2026.

Two other limits are time limits. A run's lock is treated as expired after 45 minutes, so a run killed part way cannot block every later run. Any network git command is retried four times with a 20, 40, 60 second backoff, and if all attempts fail with an error that looks like a network failure the run exits quietly with status 0 and sends no email. The LinkedIn step has its own cap, AINEWS_LINKEDIN_TIMEOUT_S = 900 seconds, and a failure in that step never fails the run: the item is published without a post and the gap is filled by hand later.

What the job measures about its own cost, and what it does not

The writer step, which does the main work, is called with --output-format text. Anthropic's non-interactive page describes text as the default, plain text output, and says that with --output-format json the response payload includes total_cost_usd and a per-model cost breakdown so scripted callers can track spend without the usage dashboard [1]. The job therefore records no token count and no dollar figure for its writer, and the project's own notes say "Cost is unmeasured" for that step. This is the job's largest gap. The fix is one flag, --output-format json, and the parsing the LinkedIn step already does.

The LinkedIn step is called with --output-format json and records total_cost_usd per call. Measured on 21 September 2026 over 6 calls: $0.78 to $2.33 per item, 29 to 256 seconds. The most recent run's log, read on 4 October 2026, shows 2 calls, $1.25 in 69 seconds and $1.31 in 86 seconds, $2.56 for the run. These are dollar figures reported by Claude Code at list price for the opus model; Anthropic's cost page says Claude Code computes its cost figures locally from token counts at list price unless a modelPricing table from managed settings is in effect [3]. Anthropic's Agent SDK page calls total_cost_usd a client-side estimate computed from a price table bundled at build time, which can differ from the bill, and says to take authoritative figures from the Usage and Cost API or the Console [7]. The page on where to see Claude Code spend covers those sources.

No spend cap is set on either call. Anthropic's CLI reference describes --max-budget-usd as the maximum dollar amount to spend on API calls before stopping, print mode only (print mode is the -p form), with spend from subagents counted, and says that once spend reaches the cap, starting another subagent fails with Budget limit reached and running background subagents are stopped, with enforcement from Claude Code v2.1.217 [2]. The caps the job has are time caps.

The alerting split

Every failure path inside the run emails the owner, best-effort: a missing mail tool must never cause a failure of its own. A separate scheduled job, a watchdog (a job whose only task is to check that the main job ran) at minute 37 of every hour, emails once when no successful run has been recorded in 3 hours and is ready to email again once a run succeeds. It is separate on purpose. The failure that happened on 6 September 2026 was the job never starting, which no code inside the job could report. A stalled writer and an offline hour both exit quietly, with no email, and both withhold the success timestamp, so the watchdog still reports a real stall once instead of the run emailing every hour.

Each cap, its value, what it protects against, and the incident behind it

Cap Value What it protects against The incident behind it
AINEWS_WRITER_TIMEOUT_S, per attempt 1,500 seconds A writer that never exits holding up the hour's run 10 September 2026: 5 hours 47 minutes, five hourly runs lost
AINEWS_WRITER_TRIES 3 attempts A transient API failure costing the whole hour 11 and 12 September 2026: hourly emails for failures the next run fixed
AINEWS_WRITER_BACKOFF_S 60 seconds Attempts in quick succession against an API that is already failing 12 September 2026 test: 14 attempts in 71 seconds
AINEWS_WRITER_BUDGET_S 2,700 seconds Retries running into the next hour's run The same test; a retry starts only if a full attempt plus its backoff fits
AINEWS_LINKEDIN_TIMEOUT_S 900 seconds The second step holding the run; its failure never fails the run Set with the step
Lock staleness 45 minutes A run killed part way blocking every later run Design
Network git retries 4 attempts, 20, 40, 60 seconds apart An offline hour counted as a failure Design: an offline hour exits 0 with no email
Watchdog Emails once after 3 hours with no success A job that never starts 6 September 2026: the job had stopped running
--max-budget-usd Unset on both calls Dollars per run None yet; this page recommends adding it

What to add, in order

Reveneau recommends two changes to this job, and the order matters. First, change the writer's call to --output-format json and record total_cost_usd from every run, as the LinkedIn step does. Second, once the record covers enough runs to show the largest healthy one, add --max-budget-usd to both calls with a figure above the largest healthy run. For the LinkedIn step the measurement already exists: its largest measured call is $2.33, so a cap at Anthropic's documentation example of $5.00 would be higher than every call measured so far (our comparison of the two figures, which says nothing about future calls). For the writer, no figure exists yet, so a cap set today would be a guess, and a guess set too low ends good runs while a guess set too high protects nothing.

Two facts from Anthropic's documentation make the cap worth adding even with the time caps in place. Spend from subagents counts toward it, and a writing skill is free to delegate, so a run that starts subagents is bounded in dollars where the attempt clock bounds only its elapsed time [2]. And the flag is enforced where the job already runs, in print mode [2]. The page on when a subagent saves tokens covers what a delegated run adds.

Our position

This job is the pattern Reveneau recommends for an unattended run, with one gap stated plainly. A plain script with no model does the checking, so most hours start no model. The model runs once, with its model pinned, its tools listed, its output in a file, and every process it started ended by the runner. Attempts are counted, spaced and budgeted, and a retry runs only on a clean copy. Failures that a person can act on send one email, and a job that never starts is caught by something outside it. What it lacks is a cost record for its main step and a dollar cap on either call, and the fix for both starts with one flag. Every figure above is one job's figure, read from its own logs on 4 October 2026, and Reveneau is independent of Anthropic, whose documentation supplied every statement here about what the flags do.

Common questions

What is Reveneau's hourly AI News job?

It is an unattended job that writes short news items for the AI News section of reveneau.com. By its scripts read on 4 October 2026, the operating system's scheduler starts it at minute 7 of every hour; a script with no model polls the news sources; `claude -p` runs once with a writing skill; a second script writes a LinkedIn post per item; the site builds; a publish script commits and pushes. No person reviews an item before publication, authorised by the site owner on 31 August 2026.

Are the figures on this page about Reveneau's job a statistic?

No. Every figure on this page was read from one job's scripts and logs on 4 October 2026, and it describes that job on the days named. Forty writer passes over three days in September 2026 and six LinkedIn calls on one day say what that job did then, on that machine, with that skill, and nothing about another job or another week. The page gives them so a reader can see what measuring an unattended run looks like, and what one job left unmeasured.

Why does Reveneau's job pin --model opus?

Because without `--model`, `claude -p` uses the model a person last saved in their settings file, and on 7 September 2026 that file named a model the account could not use. Six hourly runs between 02:07 and 09:07 PT failed with `There's an issue with the selected model`, by the job's logs read on 4 October 2026. Anthropic's error reference gives the remedy for `-p` as passing `--model` with a valid alias or ID, and the model has been pinned in the script since.

Why does the job cap each writer attempt at 1,500 seconds?

Because across 40 healthy writer passes from 10 to 12 September 2026, by the job's logs read on 4 October 2026, the shortest took 323 seconds, the median 648, the 90th percentile 952 and the longest 1,201, so an attempt still running at 1,500 seconds is stuck. The cap came down from 1,800 after that measurement. The script polls the time itself because macOS ships no `timeout` command. Three attempts are allowed, 60 seconds apart, inside a 2,700-second budget.

What happened in the 10 September 2026 hang?

The 17:07 PT run stayed open for 5 hours 47 minutes and blocked the next five hourly runs, by the job's logs read on 4 October 2026. The writer finished its item by 17:21, then started a development server to confirm the page rendered, because its skill asked for that check. The server never exits, it had inherited the writer's output pipe (the channel carrying the writer's output), and the runner waited until every process holding the pipe exited. The writer's attempt to stop it with `pkill` was refused.

Why are kill and pkill left out of the writer's allowed tools?

Because the runner owns process lifetime, by the job's scripts read on 4 October 2026. After the hang, the runner was changed to end the writer's whole process group after it exits, on every path including a clean one, and to start and stop the page-checking server itself on port 4479. Giving the writer `kill` or `pkill` would let an unattended model end any process on the machine, to solve a problem the runner now prevents. Anthropic's documentation says a `-p` run denies a tool call that would otherwise prompt.

Why is the writer step's cost unmeasured?

Because the writer is called with `--output-format text`, which Anthropic's non-interactive page, read on 4 October 2026, describes as the default plain text output, and the job therefore records no token count and no dollar figure for it. The project's own notes say Cost is unmeasured for that step. The fix is `--output-format json`, which by the same page returns `total_cost_usd` and a per-model cost breakdown; the job's LinkedIn step already uses it.

What did the LinkedIn step cost per call?

Measured on 21 September 2026 over 6 calls, by the job's logs read on 4 October 2026: $0.78 to $2.33 per item, taking 29 to 256 seconds. The most recent run's log, read the same day, shows 2 calls, $1.25 in 69 seconds and $1.31 in 86 seconds, $2.56 for the run. These are the `total_cost_usd` figures Claude Code reports at list price for the `opus` model, which Anthropic's Agent SDK page calls client-side estimates that can differ from the bill.

Why does the job have a separate watchdog?

Because every alert inside the run needs the run to be alive, and on 6 September 2026 the failure was the job never starting, which no code inside it could report, by the job's notes read on 4 October 2026. A separate scheduled job at minute 37 of every hour emails once when no successful run has been recorded in 3 hours and resets once a run succeeds. The run itself also emails on every failure path, best-effort, so that a missing mail tool never causes a failure.

Why does a stalled writer send no email?

Because the email was never actionable. Between 22:08 on 11 September and 09:17 on 12 September 2026, six emails went out from the hourly run against one from the watchdog, and the watchdog's was the useful one, by the job's logs read on 4 October 2026. A writer whose attempts all reach the cap now exits quietly and withholds the success timestamp, as does an offline hour, so the watchdog still reports a real stall once instead of the run emailing every hour.

Does Reveneau's job set --max-budget-usd?

No. By its scripts read on 4 October 2026, neither `claude -p` call sets `--max-budget-usd`; every cap in the job is a time cap. Anthropic's CLI reference describes the flag as the maximum dollar amount to spend on API calls before stopping, print mode only, with subagent spend counted and enforcement from Claude Code v2.1.217. This page recommends adding it to both calls, with the figure set from measured `total_cost_usd` values, which means measuring the writer step first.

How does the job stop a retry loop from running many attempts in a minute?

With an attempt count and a wait between attempts, beside the time budget. In an instant-failure test on 12 September 2026, by the job's logs read on 4 October 2026, the loop ran 14 attempts in 71 seconds before those two were added, because a time budget alone bounds nothing when each attempt fails in seconds. The rule now is 3 attempts of up to 1,500 seconds, 60 seconds apart, inside 2,700 seconds, and a retry starts only if a full attempt plus its wait still fits.