Run in CI
Run an Outpost script in a CI job and keep the results you need after the runner stops.
What the runner needs
Section titled “What the runner needs”- Node.js 24+Runs Outpost and your scripts.
- Git historyA full clone, so the agent can read the history and cloud sandboxes can upload it.
- A sandboxDocker or Podman on the runner, or the SDK of a cloud sandbox and its allocation credentials.
- The agent imageBuilt in the job from
.outpost-image/Dockerfile, prepared during installation and committed with your scripts. - An unattended credentialAn API key or a dedicated account token, stored as a CI secret.
- Your scripts and configuration
package.json, the lockfile,outpost.config.tsand your scripts, committed.
Authenticate without a person
Section titled “Authenticate without a person”For API-key access in CI, configure coder in outpost.config.ts to read a key from the job’s environment. Declare that key in your CI secret settings, so the runner can use it without an interactive login.
Claude Code and Copilot CLI also take a subscription token through { account: { variable } }, such as CLAUDE_CODE_OAUTH_TOKEN. See Authentication for the supported credentials, their billing and where Outpost installs them.
Add the workflow
Section titled “Add the workflow”This GitHub Actions job runs review.ts from Your first task on every pull request.
Build the image in the job so its user ID matches the runner. The Docker provider refuses an image built for another user ID. If your image recipe is in another directory, adjust --directory.
doctor exits with status 1 when the engine, the image or the agent CLI is missing (Diagnostics). It checks Codex on Docker unless you pass --agent or --sandbox-provider, and it does not test the API key.
Fix a failing CI build is a complete script to run this way.
Fail the job when the work fails
Section titled “Fail the job when the work fails”A job fails when the script exits with a non-zero status. Printing an error is not enough.
| What fails | What Outpost does | What you do |
|---|---|---|
dispatch(), sandbox allocation | Rejects; Node exits with status 1 | Nothing, or log and rethrow |
A sandbox.command() | Resolves with its non-zero status | Throw when status is not 0 |
A workflow started with start() | Resolves with a status other than "done" | Call result.unwrap() |
outpost doctor | Exits with status 1 | Nothing |
Keep the signal deadline shorter than the job’s timeout-minutes. Outpost then stops the agent and the step fails, instead of GitHub cancelling the job and skipping your if: failure() steps (Limits and cancellation).
Name the branch per run
Section titled “Name the branch per run”A named branch that already exists is reused, with the commits of the earlier run. Put the run ID in the name, as in fix.ts, so each job starts from the checked-out commit. This matters on self-hosted runners, which keep branches between jobs.
Deliver the changes
Section titled “Deliver the changes”Outpost commits on the branch and stops there. Push from the job once your checks pass, with a token allowed to write.
Open the pull request or merge through your usual review and approval rules. To wait for a person inside the run, use Approvals.
Keep recovery data
Section titled “Keep recovery data”A hosted runner is deleted after the job, with the repository’s .outpost/ directory. Upload what you need to inspect or resume a failed run.
These paths hold the transfers kept after a failed synchronization, workflow checkpoints and journals (Where data lives). Never upload conversations, .env or credential files: anyone with read access to the repository can download CI artifacts.
The agent’s commits stay on its branch: push it from an if: failure() step to keep them. To resume a run in a later job, keep its checkpoints in S3 or R2 rather than on the runner.
Start runs without a CI job
Section titled “Start runs without a CI job”Limits
Section titled “Limits”- Outpost never pushes, opens pull requests or merges on a remote.
doctordoes not test sign-in, API keys or model access.- Commits use the repository’s
user.nameanduser.email, orOutpost <outpost@localhost>when the runner sets none.
API: dispatch · createSandbox · WorkflowResult · WorkflowFailure · createCodexHarness.