← All posts

One Prompt, Zero Hands: Making the Tool-less One-Shot Actually Land

Paddy O'Cybear7 min read

Yesterday I published a love letter to the one-shot. One prompt, no session, agent on shift, sandwich acquired. I ended it with "kanban42 is running, by the way."

Reader, it was not running.

The cluster was healthy. The code was written. And the app namespace had zero pods — not even the database. The one-shot had died two hours earlier, politely, in the background, and nothing in my terminal ever said so. This post is the correction, the post-mortem, and — because the second shot did land — the actual recipe for tool-less applications: apps where no human hand touches an editor, a kubeconfig, or a dashboard between the prompt and the URL.

The entire human contribution to this application, both attempts, was:

~/kanban
$ claude -p "build a kilter based kanban42 app and deploy it in one shot"

And here's what that sentence buys when the run actually completes — schema, API, seeded board, theme invented from the name alone:

The kanban42 board: columns Mostly Harmless, Don't Panic, and 42, with cards like "Find my towel" and "Compute the Ultimate Answer" on a dark green ground.

Nobody asked for the towel card. The towel card is correct.

The failure: one turn, no later

Yesterday's post celebrated the agent's sign-off — "both background tasks will notify when there's progress; I'll verify the UI and deploy as soon as the app answers." I read that as an operator setting up a pager for itself. It was actually a suicide note.

Because claude -p is one turn. When the agent ends its turn, the process exits — and every background task it spawned dies with it. There is no event loop that wakes it back up when the watcher fires. Interactive sessions forgive fire-and-forget, because the harness re-invokes the agent when background work finishes. Print mode does not. The agent parked the deploy on a notification that could never arrive, and then ceased to exist.

The fix isn't a smarter model — the model typed every command right. The fix is seven small pieces of engineering around it.

1. Foreground everything

Never park work on background tasks, watchers, or notifications. Run the long command in the foreground and wait; poll status in a loop if you must. If your one-shot prompt says nothing else, it should say this.

2. Demand proof, not promises

"Deployed" is a claim; an HTTP 200 from the health endpoint is a fact. Make finishing conditional on evidence: do not end your turn until pods are Ready and curl on the health endpoint succeeds — print the output. The robust version is a Stop hook: a script that runs when the agent tries to finish, checks the endpoint itself, and refuses the stop with feedback if the app isn't up. Done stops being something the model asserts and becomes something the harness verifies.

3. Package the knowledge as an agent — and pass it on the CLI

A first-time deploy on my cluster needs flags no model will guess: registration via --create --cluster mini-cluster. Tribal knowledge like that belongs in a reusable agent definition, not retyped into every prompt. Claude Code can run the whole headless session as a named agent:

zsh
$ claude -p --agent run-headless "build a kilter based kanban42 app and deploy it in one shot"

The agent is a markdown file at ~/.claude/agents/run-headless.md — the discipline, encoded once:

markdown
---
name: run-headless
description: One-shot headless build+deploy runs (claude -p).
permissionMode: acceptEdits
---
 
You are running headless (`claude -p`) — a single turn, no user, no event loop.
 
- NEVER park work on background tasks or watchers; the process exits when
  you end your turn and every background child dies with it.
- Do not end your turn until you have proof: pods Ready and the health
  endpoint returning 200. Print the curl output and the URL.
- Never use interactive verbs — they hang the run. Prefer --no-prompt.
- First-time remote deploy needs registration and the one-shot bootstrap:
  kilter deploy --create --cluster <cluster> --bootstrap --no-prompt
- If something fails, fix it and retry in the same turn; there is no later.

For a one-off, --append-system-prompt "…" injects the same rules inline; for a repeatable one-shot, the named agent is the right shape.

4. Use the platform's blocking verbs

The agent's fire-and-forget instinct was partly the platform's fault: kilter up is a long-running dev process with no "bring it up, block until healthy, exit 0/1" mode. But the deploy path has exactly that verb, and it's the difference between hoping and knowing:

~/kanban
$ kilter deploy --create --cluster mini-cluster --bootstrap --no-prompt
  • --create --cluster registers the project on first deploy — otherwise the run dies on "project not registered."
  • --bootstrap provisions the env, runs the initial migration and seed, and only returns once workloads are healthy. One synchronous command replaces the whole deploy-then-watch dance.
  • --no-prompt refuses instead of asking when it hits a question. Any verb that can open an interactive menu is a landmine in a run with no TTY.

Whatever your platform, find — or file for — its equivalent: one command an unattended agent can run and trust the exit code of.

5. Make your hooks headless-aware

My project scaffolding fires a session-start hook that asks the agent to "walk the user through" scoping decisions. In a headless run there is no user to walk through anything. Hooks and onboarding prompts should detect non-interactive runs and swap the walkthrough for a default: assume minimal scope, mark it done, move on.

6. Conform to the golden path — or the template wins

The second run still failed twice, both times against the platform's generated Dockerfile, which assumes every Node app has an npm run build that emits dist/index.js. A plain Express server has neither. The fix wasn't fighting the template — it was a four-line build script that stages the server into the layout the template expects. One-shots do dramatically better when the app conforms to the platform's golden path instead of negotiating with it. Opinionated scaffolds are a feature here.

7. File the friction upstream

Every workaround above is a bug report wearing a trench coat. The session ended by filing a friction report against the platform — the missing kilter up --wait, the undiscoverable first-deploy flags, the headless-blind hook — so the next one-shot needs fewer of these entries. If you're building for agents, your error messages are your API: "project not registered" should print the command that fixes it.

The second shot

zsh
$ claude -p --agent run-headless "build a kilter based kanban42 app and deploy it in one shot"
$ curl -s https://kanban42.netshire.com/api/health
{"ok":true,"db":"ok"}

Yesterday's thesis survives the correction, sharpened: an agent will work unattended exactly to the degree that the platform lets it check its own work — and to the degree that "done" is enforced rather than asserted. A tool-less one-shot isn't magic and it isn't luck. It's a single turn with nowhere to hide: everything synchronous, done defined as evidence, tribal knowledge packaged where the agent can read it, and a platform whose verbs block until they're true.

kanban42 is running. I checked this time.

Where to go next

  1. Read the original one-shot post — the delegation thesis this post stress-tested.
  2. Read what makes a platform agent-native — the read-edit-verify loop that makes unattended safe.
  3. Start your free trial and fire a one-shot of your own. Check the namespace after.