> ## Documentation Index
> Fetch the complete documentation index at: https://preloop.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboarding an AI agent

> Drive the GitHub Actions to Preloop migration from Claude Code, Codex or any agent that can run shell commands.

`preloop migrate` is built to be driven by an AI agent. Every step is a command
that prints either a short human summary or one JSON document, and the four
decisions that must not be automated are explicit gates.

## Commands

```sh theme={null}
preloop migrate plan --json     # verdicts, secrets (names only), commands, gates
preloop migrate apply --dry-run --json
preloop migrate apply --yes --json
preloop migrate verify --json   # 0 done, 10 actions remain, 3 gh/git problem
preloop migrate guide           # the playbook below
preloop migrate guide --format markdown
```

`guide` prints the playbook from one source file, so the text below and the
text an agent receives are identical; a unit test fails if they drift.

## Machine-readable surface

`plan --json` returns one document with `schema_version: 1`:

| field | meaning |
| - | - |
| `repo` | `owner/repo`, or `null` when the checkout has no `origin` (pass `--repo`) |
| `workflows[]` | `path`, `name`, `verdict`, `reasons[]` (stable `code`), `runs_on`, `secrets`, `cloud_trust`, `vault_fetch_actions`, `pinned_to_environment`, `will_edit` |
| `secrets.plain[]` | `{name, workflows}` (workflow file names) for names the human imports with `preloop secret set` |
| `secrets.vault_fetched[]` | `{name, workflows}` fetched from a vault at run time; OIDC trust only |
| `trust_snippets_needed[]` | cloud providers (`aws`, `gcp`, `azure`, `vault`) that need OIDC trust |
| `commands` | `apply`, `disable[]`, `verify` |
| `human_gates[]` | `id`, `when`, `question` for each mandatory stop |
| `next_actions[]` | the sequence, in order |

Secret names only: values are never read, printed or serialized. `plan` reads
`.github/workflows` and `git remote`; it never contacts GitHub.

`apply --json` returns `schema_version`, `dry_run`, `applied`, `branch`,
`commit`, `pr_url`, `changed_files`, `diff`, `diff_lines`, `manual_review`
(including `${{ }}` expressions and mapping forms left for a human),
`disable_command` and `next_step`. In `--json` mode stdout carries exactly one
document and progress goes to stderr. Without a terminal and without `--yes`,
`apply` exits 2 and changes nothing.

`verify --json` returns `schema_version`, `repo`, `ok`, `workflows[]`
(`path`, `runs_on_rewritten`, `github_state`, `drift`) and `remaining[]`.
`github_state` is `disabled`, `active`, `missing` or `unknown`. `drift` is true
when the local workflow still declares GitHub-hosted `runs-on` values; run
`preloop migrate apply --yes --json` again to fix it.

## Exit codes

| code | meaning |
| - | - |
| `0` | success |
| `1` | unexpected failure |
| `2` | usage: bad flag, missing `--repo`, `--yes` missing in a non-interactive shell |
| `3` | `git` or `gh` missing, unauthenticated, or unable to reach GitHub |
| `10` | `verify` succeeded and actions remain; not an error |

When `verify` exits 3, the fix is `gh auth login` (with `git`/`gh` installed).
Never handle tokens to work around it.

## Human gates

An agent must stop and ask before opening the pull request, running
`gh workflow disable`, setting any secret, or overriding a `stays_on_github`
verdict. The full `when`/`question` text is in `plan --json.human_gates` and in
the playbook below.

## Owned workflows

When the engine has the owned-workflows endpoint (`GET`, `PUT` and `DELETE`
`/api/v1/owned-workflows/{owner}/{repo}`, plus `/drift`), it is the server-side
record of which workflows Preloop owns. It is available when the engine has
that endpoint; `preloop migrate verify` does not depend on it and checks the
local files and GitHub state directly.

## Playbook

Everything below is the exact text `preloop migrate guide` prints.

````
# Migrate a repository from GitHub Actions to Preloop

You are an AI agent driving `preloop migrate` for one repository. Preloop runs
the same workflow files GitHub does. The migration rewrites `runs-on` in the
workflows that move; the human disables those workflows on GitHub after the
pull request merges.

## Goal

- Move CI (lint, test and build on Linux) to Preloop.
- Keep release, publish, deploy, tag and `environment:` workflows on GitHub.
- Never read, print, store or transmit a secret value.
- Never edit `.github/workflows/` by hand; only `preloop migrate apply` writes
  those files.
- Stop at every human gate.

## Per-workflow model

Migration is per workflow, not per job: jobs in one workflow share `needs:`,
artifacts and caches, so a workflow cannot be split between GitHub and Preloop.
Each workflow gets exactly one verdict:

- `moves` — CI. `apply` rewrites its `runs-on` values and Preloop owns it.
- `stays_on_github` — release, publish, deploy or tag work, or a job pinned to
  a GitHub `environment:`. Trusted publishing (npm, PyPI, crates.io) only
  accepts GitHub's OIDC issuer, so these must not move.
- `unsupported` — a job needs macOS, Windows or a GPU. The pool is Linux, and a
  mixed workflow is unsupported as a whole.

`will_edit` tells you whether `apply` will change the file. A `moves` workflow
with `will_edit: false` already targets a served label, or has only values a
human must edit (see `manual_review`).

## Sequence

Run the commands in this order, from the repository root.

1. Run `preloop migrate plan --json`. Read every `workflows[].verdict`,
   `reasons[]`, `will_edit` and `manual_review`-relevant field.
2. Review the verdicts. Do not reclassify a workflow yourself.
3. Explain to the human which workflows stay on GitHub (and why) and which are
   unsupported. Quote the reason messages; do not invent reasons.
4. Run `preloop migrate apply --dry-run --json`. Show the human
   `changed_files`, `diff` and `manual_review`.
5. STOP. Ask the human for gate `open_pull_request`. If the answer is no, stop;
   nothing has been written.
6. Run `preloop migrate apply --yes --json`. Add `--no-pr` when the human wants
   to push the branch and open the pull request themselves; then push only
   after they approve.
7. After the pull request merges, STOP. Ask the human for gate
   `disable_workflow`, then run each command in `commands.disable` exactly as
   printed. Run them after the merge, never before.
8. If `secrets.plain` is not empty, STOP. Ask the human for gate `set_secret`;
   the human runs `preloop secret set <NAME>` and enters the value. You only
   name the secrets. If `secrets.vault_fetched` is not empty, nothing is
   imported: the workflow fetches those at run time.
9. If `trust_snippets_needed` is not empty, tell the human which cloud
   providers need OIDC trust configured in the engine. Do not invent
   configuration or URLs.
10. Run `preloop migrate verify --json`. Handle the exit code as below and
    repeat the remaining actions until it exits 0 (or stop on 3).
11. Report using the template at the end.

All commands accept `--dir <path>`. `plan`, `verify` and `status` accept
`--repo owner/repo` when the checkout is not the repository root or has no
GitHub `origin`. `--json` writes exactly one JSON document to stdout; progress
goes to stderr.

## Human gates

Stop and ask before each of these. Never assume approval.

| id | Ask before |
|---|---|
| `open_pull_request` | pushing a branch or opening the migration pull request |
| `disable_workflow` | running any `gh workflow disable` |
| `set_secret` | any secret is imported, entered or changed |
| `override_verdict` | overriding a `stays_on_github` or `unsupported` verdict |

The plan's `human_gates[]` carries the exact `when` and `question` for each.
An override is `preloop migrate apply --override '<path>=moves'`; use it only
with explicit human approval.

## Exit codes

| code | meaning | what to do |
|---|---|---|
| 0 | success, or verification found nothing left | report; finish |
| 1 | unexpected failure | show stderr to the human; stop |
| 2 | usage error (bad flag, missing `--repo`, no TTY without `--yes`) | fix the invocation; never retry with a prompt |
| 3 | `git`/`gh` missing, unauthenticated or unable to reach GitHub | stop; ask the human to install the tool or run `gh auth login`; retry |
| 10 | verification succeeded and actions remain (not an error) | run the actions in `remaining[]`; then verify again |

`apply` in a non-interactive shell without `--yes` exits 2 and changes
nothing. Do not try to fake a terminal.

## Verdict handling

- `moves` + `will_edit: true`: normal path; `apply` rewrites it.
- `moves` + `will_edit: false`: read `manual_review`. `${{ }}` expressions and
  mapping forms (`group:`/`labels:`) are left for a human; do not hand-edit
  them.
- `stays_on_github`: leave the file alone. Explain the reason. The only way to
  change this is a human-approved `--override`.
- `unsupported`: leave the file alone. Explain the reason (macOS, Windows or
  GPU). Preloop cannot host it; do not override to `moves`.

## Secrets

- `secrets.plain` and `secrets.vault_fetched` contain names only. That is all
  you may read, print or store.
- NEVER read a value: no `env` files, no `printenv`, no `cat` of credential
  files, no value in a prompt, log, commit, issue or report.
- The human enters plain values with `preloop secret set <NAME>`.
- Vault-fetched names need no import; only the OIDC trust named in
  `trust_snippets_needed`.
- If a workflow uses `secrets.X`, report `X`; never its content.

## If `gh` is missing or unauthenticated

`verify`, `status` and `apply` need `gh`; `plan` and `scan` do not. `apply
--no-pr` stops after the local commit and needs only `git`. If `gh` is missing,
tell the human to install the GitHub CLI. If `gh api` fails or exit 3 reports
authentication, stop and tell the human to run `gh auth login`, then re-run the
same command. Do not paste tokens into commands or environment variables.

## Never

- Never run `gh workflow disable` without gate `disable_workflow`.
- Never open, push or force-push a pull request without gate
  `open_pull_request`.
- Never call `preloop secret set` or otherwise supply a secret value.
- Never run `--override` without gate `override_verdict`.
- Never hand-edit `.github/workflows/`; re-run `preloop migrate apply` instead.
- Never invent a verdict, a reason, a URL or a `gh` command.
- Never retry a non-interactive `apply` with a piped `yes`; pass `--yes` only
  after the human approved.

## Checklist

- [ ] `plan --json` read and every verdict explained to the human.
- [ ] `apply --dry-run --json` shown; human approved the pull request.
- [ ] `apply --yes --json` finished; branch, commit and pull request reported.
- [ ] Pull request merged by the human.
- [ ] Every `commands.disable` entry approved, then run.
- [ ] Plain secret names given to the human; no value seen or printed.
- [ ] `verify --json` exits 0, or the remaining actions were completed.
- [ ] Summary report delivered.

## Summary report

Use this template. Keep it short; no secret values, no marketing.

```text
Migration report for <owner/repo>
- moved: <workflow paths>
- stayed on GitHub: <path> — <reason>
- unsupported: <path> — <reason>
- pull request: <url or "local commit only">
- disabled on GitHub: <workflow files>
- secrets for the human to enter (names only): <names, or "none">
- OIDC trust needed: <providers, or "none">
- manual review left: <path/job/detail, or "none">
- verify: passed / actions remain: <remaining[]>
```
````


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.