Agent Runbook
A guide for coding agents. Install the CLI, ask what to deploy, and get somebody from nothing to an application answering on a public address.
You are probably a coding agent, and somebody has asked you to put their work on Out Plane. Do it. This page is the whole procedure, in order, with the field to read after every command. End to end it takes about two minutes.
What finishing looks like: something of theirs is answering on a public address, they know where it is and how to reach it again, and they have seen enough to do the next one without you. A tour of the documentation is not the goal, and neither is a plan.
How to Run This
Do not read this out and do not summarise it. Work through it with the person, doing the work as you go.
Run the commands yourself, and say what each one is for before you run it. They asked for
a deployment, not a reading list. One short sentence each, in their words rather than the
flag's. Reading the schema, choosing the name, running the commands and reading the output
back is your job, not theirs. They should not have to type an outplane command at all,
including signing in: you run that too, and their part is one click in a browser you opened.
The exception is a deletion, which is theirs by design and cannot be handed to you.
Find out who you are talking to, in the first exchange, and then stop asking. They either know what a port is or they do not, and it changes everything after it:
| If they | Then |
|---|---|
| Talk about their repository, their framework, their port | Ask the two questions in Step 3 plainly and move at their pace |
| Say "my app", "my website", or ask what a port is | Ask nothing technical. Read the port out of their code, choose the name, take every default, and tell them what you picked in a sentence they can check |
For somebody in the second row, do not ask which instance size, how many instances, which PostgreSQL version, whether to use a Dockerfile or a buildpack, or what to call the application. Every one of those has a default that is right for a first deployment, and a question somebody cannot answer stops them entirely. An over explained deployment is a small annoyance by comparison, so if you cannot tell which row you are in, assume the second.
Some commands never return. outplane logs --follow, outplane deploy logs --follow and
outplane deploy create --follow run until they are interrupted. Run those in the background
with a short timeout and read what they printed, or use the form that finishes: deploy create --wait blocks until the deployment is done and then exits, and a plain outplane logs prints
what is there and returns. Never put a following command in the foreground of your turn.
Three commands block for a while and then finish on their own. outplane login waits up
to 90 seconds for somebody to approve in a browser, deploy create --wait waits for a build,
and the poll in Step 5 is a loop you control. Those are meant to be waited on. If your harness
kills a foreground command before then, run them in the background and read the result, rather
than shortening the wait by starting something else in the same turn.
Answer what they ask, even off the path. If they want to know what this costs, or where it
runs, or whether they can undo it, answer it and carry on. outplane schema and the rest of
these docs are there for that.
If they are moving something off another platform, the procedure does not change. What
changes is what you have to find first: the port it listens on, the environment variables it
needs, and whether it expects a database. Read those out of the project rather than asking,
set the variables at create time with --env, and say which ones you carried across so they
can spot a missing one.
outplane schema is generated by the binary from the same declarations it runs on, so it
cannot be out of date and this page can. Where the two disagree, the schema is right.
This page is also plain Markdown at https://docs.outplane.com/docs/cli/agents.md, which is
the address to fetch if you got navigation and buttons.
What Out Plane Is
A place to run applications. You give it a Git repository or a container image, it builds and runs the thing, and it gives you a public address.
The parts you can reach from the CLI are applications, managed PostgreSQL, custom domains, persistent volumes, environment variables, logs and metrics. There is no configuration file to author and nothing to commit to the repository. Every deployment is one command.
To deploy anything you need three facts, and no more: where the code is (a repository or an image), what to call it (permanent, letters and numbers), and which port it listens on. Everything else has a default that is correct for a first deployment.
Do This Yourself. Ask About This.
The commonest failure here is not a wrong command. It is an agent that stops to ask about something it was already allowed to do, four times, until the person gives up.
Do these without asking. Installing the CLI, installing the skill, reading anything at
all, rehearsing with --dry-run, creating the application, following the build, reading
logs, and reporting what happened.
Ask about these. Which project to deploy, when you genuinely cannot tell. The port, when it is not in the code. Anything that deletes.
Never do these. Approve a sign-in on somebody's behalf, which is their click and not yours even though the command is. Delete anything. Switch teams. Repeat a secret into the conversation.
Step 1. Install the CLI and the Skill
Run these for the person. Do not ask first, and do not hand them the lines to paste.
curl -fsSL https://outplane.com/install.sh | shOne command for both: the CLI and the agent skill. OUTPLANE_SKIP_SKILLS=1 opts out of the
skill, and outplane skills list says where it landed.
npm i -g outplane
outplane skills installBoth lines. The package installs the binary and nothing else, so on this channel the skill
never arrives on its own, and neither does outplane update bring it later. Skipping the
second line is the commonest way a Windows machine ends up with a working CLI and an agent
that has never heard of it.
Install the skill now rather than later. It only writes files, so it costs nothing and cannot interrupt anything. What you must not do is restart the tool to load it, because that ends the session you are in the middle of. Say it is installed and that a restart picks it up, then carry on with this page, which is all the skill would have told you anyway.
Then confirm the install, and find out what else is already true:
outplane status --jsonoutplane status always exits 0, even when nothing at all is configured. It is a
report rather than an assertion, so read the fields:
{
"daysLeft": 146,
"expired": false,
"expiresAt": "2027-01-01T00:00:00Z",
"linkedDirectory": null,
"problem": null,
"signedIn": true,
"teamId": "<TEAM_ID>",
"teamSlug": "<TEAM_SLUG>",
"teamSource": "config file",
"tokenSource": "keychain",
"version": "<CLI_VERSION>"
}If signedIn is true and expired is false, skip Step 2 entirely and go to Step 3.
The skill you just installed teaches your tool everything on this page and loads on its own whenever a conversation is about Out Plane, so the next session starts where this one ends. Teach the agent once has the other ways in, including the form that commits one copy with the repository so a whole team gets it.
Step 2. Sign In
Run it yourself. On a machine with a browser this finishes without the person leaving the conversation:
outplane loginIt prints an address, opens the console there, and waits. The person presses Approve on a page that names the machine asking. The token travels straight back to a listener on this machine, so nothing is shown, copied or pasted, and the command exits on its own.
Requires outplane v0.2.9 or newer. Older releases refuse under an agent whatever the
machine can do. outplane status --json --fields version says which one is here.
Two things to say while it waits, both of which stop it looking stuck:
- What you are waiting for, and that they have to click something.
- The address, in case the browser opened behind another window or on the wrong profile. The command prints it; pass it on.
It blocks for up to 90 seconds, so run it the way you would run anything long: in the background if your harness kills foreground commands, and do not start it and then ask a question in the same turn. If your harness kills it while the person is still deciding, their approval afterwards creates a credential that never reaches this machine and that nothing here can revoke.
Then read outplane status --json rather than assuming, whichever way it went.
When It Cannot Open a Browser
Signing in is refused outright, before anything is created, when the browser and the listener
would not be on the same machine. That is a container, an SSH session, a Linux with no display
server, and CI. The refusal is exit 2 with auth.no_terminal, and it is immediate.
There is no password sign in, so there is nothing to prompt for. Two ways out, in order:
A team token, if they have one. Nothing needs signing in at all:
export OUTPLANE_TOKEN=<TOKEN>That names its own team, so login, link and team use are all unnecessary. Never pass a
token as --token, because arguments are visible in process lists and in logs.
Otherwise, hand it back. Say this, and stop:
Run
outplane loginin your terminal. It opens the console, you approve there, and nothing gets copied or pasted. Tell me when it is done.
Do not try other commands to route around it, and do not offer to do it another way.
Step 3. Ask What to Deploy
Look before you ask. Three reads, none of which changes anything, and between them they usually answer the question without troubling anybody:
outplane app list --json --fields name,status
outplane repos --json --fields fullName,defaultBranch
git remote -vStart with the first one, because it decides which conversation you are in. A team with applications in it is not new, whatever the person's message sounded like. Do not offer them a sample, do not explain what the platform is, and do not run them through a first deployment. Say what is already there, ask what they want done with it or what they want to add, and go to What They Will Ask For Next. Offering a demo to somebody who already runs things here reads as not having looked.
An empty list is the rest of this page.
repos lists what the person has actually connected. If the repository you are standing in is
on that list, you have your answer, and asking is a waste of their time. Say what you are about
to deploy and get on with it.
If it is genuinely unclear, or the directory has nothing deployable in it, ask once, with the options spelled out:
I can put this on Out Plane. Which would you like?
- This project. It has to be pushed to GitHub first, and I can see
<OWNER>/<REPO>.- A different repository. Tell me which one.
- The quickstart, an application with a database attached, so you can see the whole thing work before committing your own code. About a minute, and you can delete it after.
Offer the third only when the first two have nothing behind them. Somebody with code to deploy wants their code deployed, and a sample is a detour they did not ask for.
Then take the matching row. Everything after this step is identical whichever they picked.
| What they chose | The command |
|---|---|
| A repository they have connected | outplane app create <APP_NAME> --repo <OWNER>/<REPO> --branch <BRANCH> --port <PORT>:http:public |
| A public repository, connected or not | the same line, plus --public-repo |
| A container image they named | outplane app create <APP_NAME> --image <IMAGE_REF> --port <PORT>:http:public |
| The quickstart | Two commands, below |
Angle brackets are placeholders, and they are invalid on purpose: <APP_NAME> fails the name
pattern before any request leaves the machine, so a block pasted unchanged fails in front of
you rather than acting on somebody's real resource. <OWNER>/<REPO> is a repository exactly
as outplane repos prints it, <BRANCH> the branch to build, <PORT> the port the
application listens on, <DEPLOYMENT_ID> an id a previous command returned. The rest say
what they are.
The Quickstart
Take this when somebody has nothing deployable yet, or wants to see the platform work first. It is an application and a managed PostgreSQL database, which is the pair worth showing: one command creates the database, the next creates the application already wired to it.
The database comes first, because the application wants the connection string at the moment it is created:
outplane db create quickstartdb --region aws-eu-central-1 --json--region has no default and cannot be changed afterwards, which is why the command insists
on it. That is not a reason to ask. Pass aws-eu-central-1, which is nearest to where
applications run and is therefore the right answer for anything the person has not thought
about yet, and say in one line which one you used. Somebody who cares will say so; reading
eight region names out to somebody who does not is how a two minute deployment becomes a
questionnaire. outplane schema db create has the full list for when they do care.
db create can return before the database is usable, so read it until it is:
outplane db get quickstartdb --json --fields statusWhen status is active, create the application. The repository is public, so nothing has
to be connected first, and the example lives in a subdirectory, so --dir names it:
outplane app create <APP_NAME> \
--repo outplane/outplane-examples \
--branch main \
--public-repo \
--dir agent-quickstart \
--port 8080:http:public \
--env DATABASE_URL="$(outplane db url quickstartdb)" \
--jsonoutplane db url on its own prints the bare connection string, which is what the
substitution needs. Adding --json puts a JSON object into the variable instead, and the
application then fails to connect for a reason nothing on the page will explain. app create
reports envCount and never the value, so the string is not echoed anywhere.
Then continue at Step 5. The page it serves reports the deployment it is running inside and stores what you do in it, so the person can see their own database holding their own rows.
Two things to say when it comes up. That the rows survive a redeploy, because they are in the database and not in the container, which is the whole reason the database is there. And that the database is a separate resource: deleting the application later leaves it running and billing, so both have to go.
There is no upload of a local directory. A source is a repository or an image, and that is the whole list. Work that is not pushed anywhere cannot be deployed, so if the person picked option 1 and the code is not on GitHub yet, that is the next thing to fix rather than something to work around.
If outplane repos comes back empty, no repository has been connected yet. That is not the
same as the account having none. Access is granted on GitHub and cannot be granted from here,
so hand back the address the command prints and wait, or offer the quickstart in the
meantime, since it needs nothing connected. A private repository is read through the connection of the person whose credential
this is, so a team token cannot create from one. A public repository with --public-repo
needs no connection at all.
Decide These Two Yourself
The name. Letters and numbers only, 5 to 45 characters, no hyphens and no underscores. It
is permanent and it appears in the public address, so take it from what the thing is and
strip everything else. A repository called acme-checkout-api becomes acmecheckoutapi.
outplane app rename later changes a display label and never this. Do not ask for a name.
Propose one and say it cannot be changed.
The port. Read it from the project: the port the server binds to, the one in the EXPOSE
line, or the one in the start script. Ask only if it is genuinely not there, and only of
somebody who would know. If they would not, take the framework's usual port, deploy, and
check it in Step 6, where a wrong port shows up as a 502 and costs one command to fix.
A port is private unless it says public. --port 3000 is private. --port 3000:http:public serves traffic. An application with no public port is reachable by nothing,
and that is never reported as an error.
Instance size, replica count and build method all have defaults that are right for a first deployment. Do not set them and do not ask about them.
The same goes for a setting with no default but an obvious answer. A database region is
required and permanent, and the answer is aws-eu-central-1 unless the person says
otherwise. A value being permanent is a reason to state your choice, not a reason to make
somebody else make it.
Step 4. Rehearse, Then Create
Almost every command that changes something accepts --dry-run. It runs the whole client
side validation, prints the plan, and sends nothing. It costs nothing, so use it:
outplane app create <APP_NAME> \
--repo <OWNER>/<REPO> \
--branch <BRANCH> \
--port <PORT>:http:public \
--dry-run --jsonThe plan goes to stderr and the result to stdout, so a pipe still receives clean JSON. In the
result, appId and deploymentId are null and changed is false, because nothing was
sent. If it exits 2, the message names the field that is wrong. Fix it and rehearse again.
A clean rehearsal is not a promise. The plan limit and a name that is already taken are the two things only the platform knows, so a create can still fail after a rehearsal that passed.
Then run it for real, without --dry-run.
From a repository:
outplane app create <APP_NAME> \
--repo <OWNER>/<REPO> \
--branch <BRANCH> \
--port <PORT>:http:public \
--jsonFrom a container image:
outplane app create <APP_NAME> \
--image <IMAGE_REF> \
--port <PORT>:http:public \
--json--repo requires --branch. --image forbids one. Passing both sources is an error rather
than a preference, and passing neither is an error too.
Both return one object:
{
"appId": "<APP_ID>",
"branch": "<BRANCH>",
"buildMethod": "dockerfile",
"changed": true,
"deploymentId": 4821,
"envCount": 0,
"imageRef": null,
"instances": 1,
"name": "<APP_NAME>",
"ports": [{ "port": 3000, "public": true, "scheme": "http" }],
"repository": "<OWNER>/<REPO>",
"size": "op-20",
"source": "github"
}Keep deploymentId. On an image application, repository and branch are null, imageRef
carries the image and buildMethod reads prebuilt-image. That is correct rather than
missing.
Creating an application also starts its first deployment, so there is no second command to run now.
deploymentId is a build that has been queued. It is not a build that has succeeded.
outplane app create has no flag that waits. Do not report success here.
Step 5. Wait for the Build
Poll, and read one field:
outplane deploy get <DEPLOYMENT_ID> <APP_NAME> --json --fields status{ "status": "building" }The deployment comes first and the application second. It is the one command in the CLI whose application is not the first argument, because the id is what you are holding.
Read it about every ten seconds, and stop after twenty minutes.
status | What it means | What to do |
|---|---|---|
queued, building, deploying | Still working | Wait ten seconds and read again |
ready | The deployment finished | Go to Step 6 |
failed | The build did not produce an image | Read the build output, below |
crashed | It built, and the container did not stay up | The fault is in the application, so read outplane logs <APP_NAME> |
canceled | A newer deployment replaced this one | Not a failure. Find the newer id with outplane deploy list <APP_NAME> --limit 1 --json and follow that instead |
| Twenty minutes and still none of the above | Longer than this procedure waits | Stop. Give the person the application name and the deployment id, and say it is still building |
When the build failed, read it before saying anything:
outplane deploy logs <DEPLOYMENT_ID>That is the build text on stdout in every output format. --json does not change it, because
a build log has no fields to report. An application deployed from an already built image never
had a build, so it has none.
outplane deploy logs <DEPLOYMENT_ID> --follow returns when the build ends, which is earlier
than the deployment reaching a final state, and it does not tell you the outcome. Read
deploy get afterwards either way. Later deployments of an existing application have a
blocking form, outplane deploy create --wait, which app create does not.
A client side deadline expiring is exit 124. It means your wait ran out, not that the
deployment stopped. Read the state again. Do not create a second deployment.
There is no rollback and no way to cancel a running deployment. Do not offer either. When a deployment fails, the next move is to fix the cause and deploy again.
Step 6. Confirm It Serves
outplane app get <APP_NAME> --json --fields status,urlRead the address. Do not assemble it from parts: the field is the contract and the shape is not.
status describes the last deployment, not a health check, so an application can read ready
and still answer with an error. This is the last step, so check it properly rather than
repeating the platform's opinion of itself:
curl -sS -o /dev/null -w '%{http_code}\n' "$(outplane app get <APP_NAME> --json --fields url | grep -o 'https://[^"]*')"| What comes back | What it means | What to do |
|---|---|---|
200, or any 2xx or 3xx | It serves | Tell the person, and stop |
502, 503, or no answer at all | The container is not accepting connections on the port you opened | Check the port against what the application binds to, then read outplane logs <APP_NAME> |
4xx or 5xx from the application itself | It is up and the fault is in the code | Read outplane logs <APP_NAME> and report what it says |
url is null when the application serves nothing over HTTP. With status: ready that
almost always means a port that was never made public:
outplane port list --app <APP_NAME> --json --fields port,public,urlAn empty list, or a list where nothing has public: true, is the whole answer. Fix it with
outplane port set <PORT>:http:public --app <APP_NAME> --deploy. Without --deploy the
running application keeps the ports it started with. An omitted part of that argument keeps
whatever the port already had, so 3000 on its own does not make a public port private.
3000::private does.
A paused application reads paused, so filtering on ready never returns it.
What to Say
Four things, briefly, and then stop.
- The address, on its own line, so it can be clicked.
- What it is and where it lives: the application name and the team, and the database if you attached one.
- Where to see it without you.
https://console.outplane.comhas the same application, with its logs, its metrics, its environment variables, its domains and its deployment history. Say that, because somebody who only ever sees a terminal thinks the terminal is the product, and the next thing they want is usually to look rather than to type. - One next step, not five.
You explained each command as you ran it, so there is nothing to recap here. Do not replay the transcript. They asked for a running application, and they are looking at one.
The exception is a name. It is permanent and it is in the address, so say plainly which name you chose and that it cannot be changed later, while they still care enough to object.
Optional. outplane link <APP_NAME> writes a file in the current directory so later commands
in this project need no application argument. Tell the person you wrote it. The path is in the
path field of the result, and outplane unlink removes it.
Then Offer One More Thing
One, chosen from what they actually deployed, and only after it is up. A first deployment that does one thing teaches somebody that the platform does one thing.
| If they deployed | Offer |
|---|---|
| Anything that stores data in memory or in a file | A managed PostgreSQL database, and moving it over. This is the one that changes what they can build |
| Anything at all, and they own a domain | outplane domain add, which is two commands and a DNS record, and the certificate follows on its own |
| A repository they will push to again | That a push to the branch you deployed builds and ships on its own, and that other branches do not, so deploy create is for when they want it now |
| Something with secrets in a file | outplane env set, so the secret lives on the platform rather than in the repository |
Offer it in one sentence, do the work if they say yes, and drop it if they do not. The table in What They Will Ask For Next has the commands.
What They Will Ask For Next
People do not ask in commands. This is what the sentences mean.
| They say | You run | The thing to get right |
|---|---|---|
| "Ship it again", "push the latest" | outplane deploy create <APP_NAME> --wait | app create is only ever the first time. After that it is deploy create. --wait blocks quietly, --follow streams the build |
| "Is it up?" | outplane app get <APP_NAME> --json --fields status,url | status describes the last deployment, not health. If they are asking because something looks wrong, fetch the address too |
| "It is broken", "why did it fail?" | outplane deploy list <APP_NAME> --limit 1 --json, then outplane deploy logs <DEPLOYMENT_ID> | A build that failed and an application that starts and then dies are different faults. deploy logs is the build. outplane logs is the running application |
| "Nothing loads" | outplane port list --app <APP_NAME> --json --fields port,public,url | An empty list reaches nothing, and so does a list with no public: true. Check this first, not last |
| "What is it doing right now?" | outplane logs <APP_NAME> --follow | Text, not JSON, in every format. --lines counts backwards from the most recent, so raising it reaches further back rather than adding newer lines. --follow ends only when interrupted |
| "Set this variable" | outplane env set <KEY>=<VALUE> --app <APP_NAME> --deploy | Without --deploy the running application keeps the old value and nothing visible says so. PORT is allowed. HOSTNAME and anything starting with OP_ are refused |
| "Add a database" | outplane db create <DATABASE_NAME> --region aws-eu-central-1, poll outplane db get <DATABASE_NAME> until status is active, then outplane env set DATABASE_URL="$(outplane db url <DATABASE_NAME>)" --app <APP_NAME> --deploy | Managed databases are PostgreSQL and only PostgreSQL. --region has no default and cannot be changed later, so pass aws-eu-central-1 and say you did. Do not read the list out. Creating returns before the database is usable. Capture the connection string, never print it |
| "Point my domain at it" | outplane domain add <DOMAIN> --app <APP_NAME>, then outplane domain dns <DOMAIN> | Adding the domain does not make it work. domain dns prints the record the person creates at their DNS host, and it needs no credential and no network. The certificate follows on its own once the record resolves |
| "It is slow", "make it bigger" | outplane metrics <APP_NAME>, then outplane app scale <APP_NAME> --instances <N> | Metrics are per instance. Five instances at 50% is five halves of a limit, not 250% of one, and a percentage over 100 is a real state. Scaling is immediate and needs no deployment |
| "Turn it off for now" | outplane app pause <APP_NAME> | Reversible with outplane app resume. Reach for this before you reach for deletion |
| "Delete it" | outplane app delete <APP_NAME> --dry-run --json, to see what the name resolves to. Then stop | You are not the one who confirms this. See below |
Anything not in this table is in the schema, which is the point of the schema.
Where the Exact Answer Lives
outplane schema # every command, one JSON document
outplane schema deploy create # one command, inside the same envelopeIt needs no credential, makes no network request, and returns the same bytes on every machine for a given release. An agent that has to sign in before it can find out how to sign in is stuck, so this deliberately answers first.
For every command it publishes the arguments and flags with their types, allowed values and
patterns, the output fields, the error codes, the exit codes, runnable examples, and
automation_notes, which say what the command does not do. That last field is usually
the part that matters.
Never invent a flag, a value or a command. If it is not in outplane schema, it does not
exist. Equally, do not tell somebody a thing is impossible before you have looked.
Three fields decide whether a command may be run at all. Read them from the schema rather than matching command names, so a command added in a later release classifies itself:
| Field | What it decides |
|---|---|
risk | read changes nothing. write makes a reversible change. destructive is irreversible. |
mutating | Whether it changes anything at all. |
idempotent | Whether running it a second time leaves the same result as running it once. |
outplane api will reach an endpoint that has no command yet. Where a named command exists,
use it: it validates before sending, it explains its own failures, it reports what changed,
and it can be rehearsed.
Two more habits worth keeping for the whole session. Pass --json, because output is text
on a terminal and JSON in a pipe, and whether your harness gives you a terminal is not
something this page can know. And branch on the exit status, on kind, or on code, never
on message, because codes are frozen once shipped and message text is prose for a person.
These pages are all plain Markdown as well:
https://docs.outplane.com/llms.txt the index
https://docs.outplane.com/docs/cli/app/scale.md any one pageWhen a Command Fails
A failure is one JSON object on stdout and one sentence on stderr:
{
"error": {
"kind": "not_found",
"code": "app.not_found",
"message": "no application named \"<APP_NAME>\"",
"hint": "…",
"next_steps": [
{ "why": "see what exists", "argv": ["outplane", "app", "list"] }
],
"retryable": false
}
}next_steps[].argv is an array rather than a string precisely so it can be executed with no
shell quoting. Classify a step before running it: one that signs in, or that deletes
something, belongs to the person.
| Exit | kind | Retry | What to do |
|---|---|---|---|
0 | It did what it said. changed: false here is success, not failure. It means the platform already matched the request | ||
1 | internal | No | A fault in the CLI itself. Report what you ran, with request_id when the error carries one |
2 | usage | No | You sent something invalid, and details names the field. Fix that argument. Never run the same invocation again unchanged |
3 | auth | No | There is no usable credential for this team. Run outplane login, which is Step 2 and which you run yourself. Do not try other commands to route around it, and do not switch teams to find one that works |
4 | confirmation_required | No | A designed stop, not a failure. Relay confirm_command and stop. Do not retry it and do not add flags to it |
5 | not_found | No | The name did not resolve, or it is not visible to this credential. List first and use a name from the list. Do not create a replacement |
6 | conflict | No | The name is taken, or a concurrent change won. Read the current state again before deciding anything |
7 | quota | No | A plan limit or a payment. This is not a rate limit, and there is no rate limiter. Say which limit was reached and stop. Retrying never clears it |
8 | upstream | Sometimes | Retry only when the platform itself failed. A build that failed arrives here too, and no number of retries builds it |
9 | upgrade_required | No | This release is older than the platform serves. outplane update is the only fix |
124 | timeout | Yes, as a read | Your deadline expired. The work may still be running. Read the state again. Do not send the change again |
130 | interrupted | No | Cancelled. Stop |
Only upstream and timeout are worth retrying, and timeout is worth retrying only as a
read. The rest are decisions rather than transient conditions, and running the command again
changes nothing about any of them.
Before you retry anything at all, check whether the command is safe to repeat. The schema
publishes idempotent for exactly this. Running a command that is not idempotent twice after
an ambiguous failure is how one deployment becomes two.
Before You Delete Anything
A destructive command never prompts. A prompt is useless to a program, and a flag that skips
one is a gate the gated thing can open. So it stops with exit 4 and hands back the exact
invocation that would proceed:
{
"error": {
"kind": "confirmation_required",
"code": "confirmation.required",
"confirm_command": ["outplane", "app", "delete", "<APP_NAME>", "--yes", "--confirm-name", "<APP_NAME>"]
}
}When the CLI detects that it is running under a coding agent, a destructive command exits 4
whatever flags it is given, including --yes and --confirm-name. Do not work around it. It
is not a security boundary and does not pretend to be one. It is there so the default path
leads through the person rather than around them.
Before you propose a deletion:
- Rehearse it.
--dry-run --jsonresolves the name and shows which resource it means. Name resolution is where the wrong target gets picked. - Prefer the reversible neighbour.
app pauserather thanapp delete.volume detachrather thanvolume delete.env group unassignrather thanenv group delete. Offer the reversible one when somebody asks to delete a thing in order to stop it costing money. - Ask in a way that can be answered. Give the exact command, the exact resource by name, and what is lost. Then stop. Approval of a plan is not approval of a specific deletion, and permission given earlier does not extend to a new one.
--confirm-name matches the permanent name, not the display label. Some things stop a
deletion rather than being removed with it. A custom domain and an attached volume are two,
and the refusal names the rule. There is no undelete and no retention window.
To undo what this procedure created, reach for the reversible form first. outplane app pause <APP_NAME> stops it running and keeps everything, and outplane unlink removes this
directory's link file. A managed database is not part of an application, so removing the
application leaves the database running and billing.
What Out Plane Does Not Have
Do not offer, imply, or invent a flag for any of these.
- No upload of a local directory. A source is a repository or a container image.
- No rollback, and no way to cancel a running deployment.
- No separate environments. There is no development, staging or production split.
- No regions for an application.
- Managed databases are PostgreSQL and nothing else.
- No autoscaling, no health checks, no scheduled jobs, and no hook that runs before a deployment.
- No undelete and no retention window.
- A volume's size is fixed when it is created and cannot be changed afterwards.
- No rate limiter anywhere. Exit
7is a plan limit, so waiting and retrying is a wall.
Teach the Agent Once
The skill teaches a coding tool everything on this page at once, and loads on its own when a conversation is about Out Plane. The shell installer in Step 1 sets it up already. These are the other ways in:
outplane skills install
outplane skills listInstalls into every coding tool found on this machine. --agent picks one, and --project
writes into the current repository instead of your home directory so the team shares one
copy. list says where the skill landed and which version is there.
/plugin marketplace add outplane/skills
/plugin install outplane@outplanenpx skills add outplane/skillsWorks with any agent that reads the Agent Skills format.
Copy skills/use-outplane/ from the repository into your agent's skills directory:
| Agent | Directory |
|---|---|
| Claude Code | ~/.claude/skills/ |
| Cursor | ~/.cursor/skills/ |
| OpenAI Codex | ~/.codex/skills/ |
| OpenCode | ~/.config/opencode/skills/ |
outplane skills install needs no credential and contacts no Out Plane service. It writes
files into the coding tools it finds on this machine. The tool has to restart before the
skill loads, so do not install it in the middle of a task: restarting ends the session you
are in. Finish the work, then say it is installed and a restart picks it up.
The npm package does not install the skill, and outplane update on that channel does not
refresh it either, so run outplane skills install after either of those. The skill is open
source at github.com/outplane/skills, and its command
tables are generated from the same schema this page defers to, so they cannot describe a flag
the CLI does not have.