Skip to content
BlogAgents9 min read

An unattended agent run has no ceiling unless you build one

nightshift works a GitHub backlog overnight with seven different agents. Its README is unusually honest about the four controls it does not have — and the arithmetic on what that omission costs is not linear in your backlog.

On 10 September 2026 a Rust CLI called nightshift went up on Show HN. You point it at a PRD — a GitHub issue — and it works your backlog overnight: "Go to sleep with a backlog and wake up with merged PRs." It drives seven agents (claude, codex, cursor, copilot, opencode, pi, antigravity), picks the lowest-numbered unblocked issue, pipes a prompt to the agent on stdin, and repeats until the children are done.

I read it because of what its README admits. Most tools in this space describe the loop and stop. This one documents, precisely, several things it deliberately does not do — and those omissions are the controls that decide what an unattended run costs and whether you can trust what it reports in the morning. That makes it a decent thing to audit your own loop against.

An unattended agent run is the only line item on your invoice that is produced while you are asleep, by a process whose transcript is thrown away.

The completion gate is the part to copy

After the agent process exits, nightshift checks that the issue is actually closed on GitHub. If it is, the loop continues. If it is not, the run stops, and the README says why: "the agent may have exited cleanly but left the issue open, which usually means something needs your attention."

That is the right shape for the gate, and it is worth being explicit about why. The check is against a property of the world — is the work item closed — rather than against the process. Exit code zero means the process ended. It says nothing whatsoever about whether the work happened. An agent loop that treats a clean exit as success is one prompt-format change, one truncated tool call or one model upgrade away from reporting progress it did not make, and the failure is invisible precisely because the process was well behaved.

The mirror image is a run that stops because the gate fired. That is the system working: it did not claim success, it handed the problem back.

What the run cannot tell you afterwards

The README is blunt in a way I appreciate: "Agent stdout and stderr are discarded in both modes", and on failure nightshift "reports the process exit status, not agent log text." For session detail, it directs you to the agent's own history.

The consequence is that at 3am you learn that a run failed and not what kind of failure it was. Exit status is not a failure class. A rate limit, a timeout, a malformed tool call, and a model that simply could not read the file all arrive as a non-zero number, and they need four different responses: one is worth retrying, one is worth resuming, and one means a person has to look at it. Without the transcript, the retry budget stops being a decision and becomes a guess.

Delegating the audit trail to the vendor's session history is a legitimate trade — it is one less thing to build — but the trail then lives in a UI rather than next to your own logs, and it is not queryable alongside the run record. I have written before about what an agent has to record so you can answer "why"; this is the same argument from the other direction, where the answer is "ask the vendor."

Resume is a number, not a state

nightshift's --issue flag is documented as: "Skip issues below this number (useful when resuming)". That combines with the selection rule — it "picks the lowest-numbered one whose blockedBy issues are all closed" — to make resume positional.

The hazard is the interaction. Every candidate below the number is invisible for the rest of that run, including any issue whose unblocking was gated behind one. So a single open issue numbered below the skip line removes its entire subtree from consideration, and the loop then stops on exactly the condition it stops on when the backlog is genuinely finished: nothing is unblocked. The stop is correct. It is the reason for the stop that is ambiguous.

This is the retry-versus-resume distinction in a new place. Resume should be keyed on what is done — the closed state of the work items — not on where you were standing when you last stopped. A number is cheap to type and impossible to verify, which is the whole problem with it.

The dependency graph is whatever GitHub says it is

"nightshift selects work from native GitHub relationships, not issue-body text," and "Bodies are not parsed for membership or ordering." Children must be real sub-issues created with --parent, must declare --blocked-by, and must carry the ready-for-agent label. It also needs the GitHub CLI at 2.94.0 or later to read those relationships at all.

The failure mode this creates is quiet. A backlog that reads as ordered to a human — sub-tasks written out as prose inside the PRD body, "this depends on #12" — is unordered to nightshift. Those sub-tasks are not work items. They are paragraphs. The run will select something else, and if the real first item never becomes unblocked, it stops early for reasons that look identical to finishing.

There is one preflight, and it is the cheapest control in the repository: --dry-run will "show planned order and first prompt without starting an agent; requested preflight still runs". Before you spend anything at all, you can read the order it intends to work in and see whether it agrees with you.

What it costs, and why the shape is not linear

The README does not mention token usage, prompt caching, or any kind of spend or budget limit. None of those words appear. That is not really a criticism of a small CLI — the ceiling is arguably not the orchestrator's job — but it does mean the arithmetic is yours, and the shape of it surprises people.

A coding agent re-sends its transcript on every turn. The input you pay for is therefore not proportional to the work done; it is every turn's transcript, summed:

input_read(T) = G × T(T+1)/2        G = new tokens added per turn

Take a session that adds 2,000 tokens per turn — a file read, a test run, a diff — and emits 300 tokens per turn. At published Claude rates, checked 11 September 2026, with Sonnet 5 at $2/$10 per MTok and Opus 5 at $5/$25:

T = 30 turns
  input  = 2,000 × 30×31/2 =   930,000 tok = 0.93 MTok
  output =           30×300 =     9,000 tok = 0.009 MTok

  Sonnet 5: 0.93 × $2 = $1.86  +  0.009 × $10 = $0.09  = $1.95 / issue
  Opus 5:   0.93 × $5 = $4.65  +  0.009 × $25 = $0.23  = $4.88 / issue

T = 60 turns
  input  = 2,000 × 60×61/2 = 3,660,000 tok = 3.66 MTok
  output =           60×300 =    18,000 tok = 0.018 MTok

  Sonnet 5: 3.66 × $2 = $7.32  +  0.018 × $10 = $0.18  = $7.50 / issue
  Opus 5:   3.66 × $5 = $18.30 +  0.018 × $25 = $0.45  = $18.75 / issue

Doubling the turns does not double the input. It multiplies it by 3.9 — 930,000 tokens becomes 3,660,000 — because the transcript is re-read every time it is extended. Over a twelve-issue backlog, the same tool with the same flags runs from $23.40 to $225.00. That is a 9.6× spread, and no point in the run reports either number.

The uncomfortable part is which variable you do not control. Turns per session is not a setting; it is how long the agent takes to finish, which is exactly the thing that inflates when an issue is hard. So the bill is not proportional to your backlog. It is proportional to your backlog times the square of how badly your worst issue stalls. One stuck problem is not one issue's worth of extra spend.

Two caveats, because the arithmetic is only as good as its inputs. The 2,000-and-300-token figures are my scenario, not measurements taken from nightshift — substitute your own and the spreadsheet changes. And if the agent CLI's own prefix caching is doing its job, the re-read term is billed at a fraction of base input, which lowers the absolute numbers and narrows the gap between the two models without changing the quadratic shape or the ordering; the break-even for that is its own calculation. Either way, the tool reports none of it, so the only place these numbers exist is the vendor's usage logs.

The four controls you bring yourself

nightshift does the genuinely hard one right — the gate checks the world, not the process — and it is honest about the rest. If you run anything unattended, these are the four you own:

  1. A ceiling, checked before the call, with a hard stop rather than an alert. Here is what that looks like in code.
  2. A failure class, not an exit code, so retries are a decision rather than a reflex.
  3. Resume keyed on state, so a restart cannot silently drop work.
  4. A transcript you own, queryable next to the run record.

None of that is a reason not to use the tool. It is the standing cost of the category: a loop that spends money and changes files while nobody is watching is a system whose controls have to be designed, and the thing that runs the loop is seldom the thing that should own them.


This is the shape of work I do on agent runtimes with human approval gates, and it is what a reliability audit is for when the loop already exists. More in retry is not resume, retries are a line item, and which feature tripled the bill. nightshift's behaviour, flags and prerequisites are quoted from its README, read 11 September 2026. Model rates from claude.com/pricing, checked 11 September 2026; the per-turn token figures are a stated scenario, not measurements.

Something here

the audit is the cheapest way to find out for certain.