Migrate GitHub Actions with your coding agent
Can I migrate GitHub Actions to Sengi with a coding agent?
Yes. Paste the prompt on this page into Claude Code, Codex or Cursor. It reads every workflow in the repository, switches each eligible job's runs-on line to sengi-standard-2-ubuntu-2404, sengi-standard-4-ubuntu-2404 or sengi-standard-8-ubuntu-2404, and opens a pull request you review and merge. No config file, no token to copy, no YAML editing. Your builds then cost up to 74% less than on GitHub.
Last checked September 17, 2026, against the running service.
It works with any agent that can run the gh CLI: Claude Code, Codex, Cursor, Copilot's agent mode, Aider. The agent works with your own credentials, in your own checkout.
What do I need before I paste it?
- The repository connected to Sengi. Sign up, choose Connect GitHub, and pick this repository. One step installs the App and connects it.
- The
ghCLI signed in as someone who can push to the repository.
The prompt checks both before it touches anything.
What is the prompt?
Copy this whole thing. An agent that fetches rather than reading a paste can take it from https://sengi.run/agent.md, which serves these same bytes as plain text.
# Migrate GitHub Actions to Sengi runners
Migrate this repository's workflows from GitHub-hosted runners to Sengi runners.
Keep GitHub Actions; change only the `runs-on` line of each supported job, and open
a pull request the user reviews.
**Prerequisites:** `gh` CLI authenticated, and the repository connected to a Sengi
project at https://app.sengi.run — installing the GitHub App alone is not enough,
see Step 1.
## Configuration
| Setting | Value |
| --- | --- |
| Default target | `sengi-standard-2-ubuntu-2404` (2 vCPU, 8 GB) |
| Larger targets | `sengi-standard-4-ubuntu-2404` (4 vCPU, 16 GB), `sengi-standard-8-ubuntu-2404` (8 vCPU, 32 GB) |
| Replaceable source labels | `ubuntu-latest`, `ubuntu-24.04` |
| Branch | `migrate-to-sengi-runners` |
Sengi runs Ubuntu 24.04 on x64 only. Every Sengi label above is a complete label —
do not abbreviate one, and do not invent a size that is not in this table.
Replace all UPPERCASE placeholders (`OWNER`, `REPO`, `BRANCH_NAME`, `HEAD_OID`,
`BASE64_CONTENT`, `FILE_NAME`, `N`, `DEFAULT_BRANCH`) with actual values from
previous steps.
## Procedure
### Step 1: Confirm the repository is connected to Sengi
Ask the user:
> Is this repository connected to a Sengi project? Go to https://app.sengi.run,
> sign up, choose **Connect GitHub**, and pick this repository when
> GitHub asks which repositories to grant. That single step installs the App and
> connects the repository. Tell me when it is done.
Installing the Sengi GitHub App straight from GitHub does **not** connect the
repository — Sengi connects it when you pick it in that flow, and a job on an
unconnected repository queues forever instead of failing. There is no API a coding
agent can read to check this, so the user's confirmation is the check.
**Do not run any commands or proceed to Step 2 until the user explicitly confirms.**
Personal repositories are supported. The repository does not need to belong to an
organization.
### Step 2: Verify CLI auth and the workflow scope
Verify `gh auth status` succeeds. If not, direct the user to install `gh` from
https://cli.github.com/ and run `gh auth login`.
Writing a file under `.github/workflows/` needs the `workflow` scope, which
`gh auth login` does not grant by default. Check for it:
```bash
gh auth status 2>&1 | grep -o "Token scopes:.*"
```
If `workflow` is not listed, stop and have the user run
`gh auth refresh -h github.com -s workflow` before you continue. Without it the
commit in Step 6 is refused, after the branch has already been created.
### Step 3: Get the repository
Ask the user for the repository (`owner/repo`) unless the working directory already
identifies one, in which case confirm it. Then read its default branch and check the
user can push to it:
```bash
gh api repos/OWNER/REPO --jq '{default_branch, can_push: .permissions.push}'
```
If `can_push` is false, stop: opening a branch needs write access.
### Step 4: Discover workflows
Fetch every workflow file in one call:
```bash
cat <<'QUERY' | gh api graphql --input -
{
"query": "query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { id nameWithOwner defaultBranchRef { name target { oid } } object(expression: \"HEAD:.github/workflows\") { ... on Tree { entries { name object { ... on Blob { text } } } } } } }",
"variables": { "owner": "OWNER", "repo": "REPO" }
}
QUERY
```
`HEAD` in that expression is a Git ref, not a placeholder — do not replace it.
If the response is truncated or errors on size, fall back to REST: read the default
branch with `gh api repos/OWNER/REPO --jq '.default_branch'`, the HEAD SHA with
`gh api repos/OWNER/REPO/git/ref/heads/DEFAULT_BRANCH --jq '.object.sha'`, the file
list with `gh api repos/OWNER/REPO/contents/.github/workflows`, and each file with
`gh api repos/OWNER/REPO/contents/.github/workflows/FILE_NAME`. The `content` field
is base64 — decode it before scanning.
Save `defaultBranchRef.name`, `.target.oid` and the `entries[]`. For each `.yml` or
`.yaml` file, read every job under `jobs:` and classify it.
**A job is eligible when all of these hold:**
- the job is an ordinary mapping with its own steps — it has no `uses:` key;
- its `runs-on:` is a single-line scalar, plain or quoted, on one line;
- that scalar contains no `${{ … }}` expression;
- that scalar is one of the replaceable source labels in the table above.
**A job is not eligible when any of these hold.** Report each with its reason:
| Shape | Reason to report |
| --- | --- |
| `runs-on: ubuntu-22.04`, `ubuntu-20.04` | Sengi runs Ubuntu 24.04 only |
| `runs-on: windows-*`, `macos-*` | Sengi runs Linux x64 only |
| `runs-on: ubuntu-latest-16-cores` and other larger runners | not a Sengi size |
| `runs-on: [self-hosted, linux, ubuntu-latest]` (an array) | Sengi takes one label, not a list — needs manual review |
| `runs-on: ${{ matrix.os }}` or any expression | the label is decided at runtime — needs manual review |
| `runs-on: { group: …, labels: [ … ] }` | group syntax — needs manual review |
| a job with `uses:` (a reusable workflow) | the runner is chosen in the called workflow |
| no `runs-on:` at all | nothing to change |
A job with a `strategy:`/`matrix:` block **is** eligible as long as its own
`runs-on:` is a literal supported label — the matrix multiplies the job, it does
not choose the runner. Only `runs-on` that reads *from* the matrix is ineligible.
**Whole files to skip,** because a targeted one-line edit is not safe in them: any
file using YAML anchors or aliases (`&name`, `*name`, `<<:`), explicit tags, a
`%YAML` directive, a duplicate key, or more than one YAML document — that means two
documents separated by a `---` line, **not** a file that merely opens with `---`,
which is ordinary and fine. Report these as "needs manual review".
Also check whether the repository lints its workflows. If `.github/actionlint.yaml`
or `.github/actionlint.yml` exists, or a workflow runs actionlint, the Sengi labels
must be declared there or that lint job fails on the pull request before any job
reaches Sengi. Add the labels you are about to use under `self-hosted-runner.labels`
in that file, in the same commit, and say so in the summary:
```yaml
self-hosted-runner:
labels:
- sengi-standard-2-ubuntu-2404
```
If the repository runs actionlint but has no config file, create it with only that
key. This is the one file other than a workflow you may change, and only for this.
Show the user a summary: the workflows that will change, the label each job moves
from and to, and every job not migrated with its reason from the table.
If nothing is eligible, stop and say so.
### Step 5: Choose sizes and preview
**Never create a pull request before the user confirms the changes.**
Default every eligible job to `sengi-standard-2-ubuntu-2404`. That is the
like-for-like size for a standard GitHub Linux runner, and it is the cheapest.
Offer the larger sizes for jobs the user says are slow or memory-hungry; each step
up doubles both the machine and the price, and one job can be a different size from
its neighbours in the same file.
Then show the exact diff per workflow. The rules for writing it:
- change **only** the `runs-on` scalar, never the surrounding lines;
- preserve the file byte for byte otherwise — indentation, key order, comments,
blank lines, quote style and the trailing newline;
- if the old label was quoted, keep the quotes and replace what is inside them;
- skip commented-out lines.
The diff a reviewer sees should be one changed line per migrated job and nothing else.
### Step 6: Open the pull request
**Branch.** Check for an existing branch first:
```bash
gh api repos/OWNER/REPO/git/matching-refs/heads/migrate-to-sengi-runners --jq '.[].ref'
```
If any exist, take the highest numeric suffix and add one — `-2` if none has a
suffix. Then create it:
```bash
gh api repos/OWNER/REPO/git/refs -f ref=refs/heads/BRANCH_NAME -f sha=HEAD_OID
```
**Commit.** One atomic commit for every file, contents base64-encoded. This creates
a verified commit without touching the user's git config:
```bash
cat <<'MUTATION' | gh api graphql --input -
{
"query": "mutation($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid url } } }",
"variables": {
"input": {
"branch": {
"repositoryNameWithOwner": "OWNER/REPO",
"branchName": "BRANCH_NAME"
},
"message": {
"headline": "Migrate N CI workflows to Sengi runners",
"body": "Replaced runners per file:\n- file1.yml: ubuntu-latest → sengi-standard-2-ubuntu-2404\n- file2.yml: ubuntu-24.04 → sengi-standard-4-ubuntu-2404"
},
"expectedHeadOid": "HEAD_OID",
"fileChanges": {
"additions": [
{ "path": ".github/workflows/FILE_NAME", "contents": "BASE64_CONTENT" }
]
}
}
}
}
MUTATION
```
Check the response carries a `commit.oid`. On an `expectedHeadOid` mismatch, read the
head of **the branch you just created** — not the default branch, whose SHA is the
one that just mismatched — and retry the commit once with that value:
```bash
gh api repos/OWNER/REPO/git/ref/heads/BRANCH_NAME --jq '.object.sha'
```
On any other error, report it and stop — never open a pull request on top of a
failed commit.
`N` is the count of changed workflow files.
**Pull request:**
```bash
gh pr create --repo OWNER/REPO --head BRANCH_NAME --base DEFAULT_BRANCH \
--title "Migrate N CI workflows to Sengi runners" --body "..."
```
Body:
```
## Summary
Moves CI from GitHub-hosted runners to Sengi runners. Same GitHub Actions, same
workflows, same logs — only the `runs-on` line changes.
## Changes
- `file1.yml`: `ubuntu-latest` → `sengi-standard-2-ubuntu-2404`
- `file2.yml`: `ubuntu-24.04` → `sengi-standard-4-ubuntu-2404`
## Not migrated
- `file3.yml` (job `build`): uses `${{ matrix.os }}` — needs manual review
(or "Every eligible job was migrated.")
## After merging
The next run of these workflows executes on Sengi. Logs, statuses and required
checks stay in GitHub. To undo it, revert this pull request.
```
### Step 7: After the merge
Offer to check the first run. When the user says the pull request is merged:
```bash
gh run list --repo OWNER/REPO --branch DEFAULT_BRANCH --limit 5 \
--json status,conclusion,name,createdAt,databaseId
```
Look for runs created after the merge.
- A job that reaches `in_progress` is working. A runner that is not already warm has
to be provisioned first, so allow around a minute before reading anything into a
job that is still queued.
- A job still `queued` after a few minutes has **three** likely causes, in this
order. Do not assert the first one without ruling out the others:
1. **The repository is not connected.** Most likely if *no* job has ever started.
Send the user to https://app.sengi.run to check this repository is listed.
2. **The free account's concurrency limit.** A free account runs at most 12 vCPU
at once — for example six 2-vCPU jobs, three 4-vCPU jobs, or one 8-vCPU job
beside two 2-vCPU jobs. Jobs over that line wait for a running one to finish,
which is normal and resolves itself.
Suspect this first when some jobs started and others did not, and especially
after migrating a matrix job that fans out wider than six.
3. **The monthly free allowance is spent.** New free jobs wait until the next UTC
month; jobs already running finish. The user can see this in their project.
- A job that fails on a step that passed on GitHub-hosted runners is worth
reporting: give the user the failing step and the run URL.
## Error handling
| Error | What to do |
| --- | --- |
| `gh` missing or logged out | Install from https://cli.github.com/, run `gh auth login` |
| Token lacks the `workflow` scope | `gh auth refresh -h github.com -s workflow`, then retry |
| The pull request's own lint fails on an unknown runner label | actionlint — declare the label under `self-hosted-runner.labels` in `.github/actionlint.yaml` |
| 403 / insufficient permissions | The user needs write access to the repository |
| Repository not found | Check the name and the user's access |
| No `.github/workflows` | Nothing to migrate; stop |
| 422 creating the branch | The branch exists — add a numeric suffix |
| `expectedHeadOid` mismatch | Re-fetch the HEAD SHA and retry the commit once |
| GraphQL response truncated | Fall back to the REST calls in Step 4 |
| Rate limit exceeded | Wait a few minutes and retry |
| Jobs stay `queued` after merge | Check all three causes in Step 7 before blaming the connection |
| A job needs Ubuntu 22.04, Windows, macOS or arm64 | Leave it on GitHub-hosted runners |
## What Sengi costs
$0.003 a minute for 2 vCPU with 8 GB, $0.006 for 4 vCPU with 16 GB, $0.012 for
8 vCPU with 32 GB. Each job's execution time rounds up to a whole minute; boot and
teardown are not billed. One account allowance covers 3,000 minutes a month on
2 vCPU, 1,500 on 4 vCPU, 750 on 8 vCPU, or any mix — it resets monthly and does not
carry over. No card, no subscription, no monthly minimum.
Reading this without JavaScript, or where the clipboard is blocked? Select the block and copy it, or run curl -s https://sengi.run/agent.md.
What does it do, in order?
- Asks you to connect the repository and waits for you to say it is done. It runs no commands until then.
- Checks
gh auth statusand that you can push to the repository. - Reads every workflow in
.github/workflowsin one GraphQL call, with a REST fallback for repositories too large for one response. - Classifies each job as eligible or not, and tells you the reason for every job it will not touch.
- Shows you the diff and the size it picked for each job, and stops for your confirmation.
- Commits once, atomically, through the Git data API, and opens the pull request against your default branch.
- Offers to watch the first run after you merge.
Which runner size does it pick?
| Label | Machine | Rate | Free minutes a month |
|---|---|---|---|
sengi-standard-2-ubuntu-2404 | 2 vCPU, 8 GB | $0.003/min | 3,000 |
sengi-standard-4-ubuntu-2404 | 4 vCPU, 16 GB | $0.006/min | 1,500 |
sengi-standard-8-ubuntu-2404 | 8 vCPU, 32 GB | $0.012/min | 750 |
Every job defaults to 2 vCPU, the like-for-like replacement for GitHub's standard Linux runner, for up to 74% less per build. Tell the agent which jobs are slow and it puts those on 4 or 8 vCPU; jobs in the same file can use different sizes. Boot and teardown are never billed.
All three run Ubuntu 24.04 x64 from the same image. To see what a month costs, use the GitHub Actions cost calculator.
What happens after you merge?
The next run of those workflows executes on Sengi, each job on its own fresh VM. Logs, statuses, annotations and required checks stay in GitHub, exactly where your team expects them. Your agent can watch the first run and confirm it landed on Sengi. To go back, revert the pull request.
Prefer to do it yourself? It is one line per job: set runs-on to a Sengi label and merge.
Frequently asked questions
Can my coding agent move my workflows to Sengi?
Yes. Copy the prompt on this page into Claude Code, Codex, Cursor or any agent that can run the GitHub CLI. It reads every workflow in the repository, finds the jobs Sengi runs, changes the runs-on line of each one, and opens a pull request for you to review. It asks before it changes anything and never merges. You connect the repository in one browser click; the agent does the rest.
What does the agent actually change?
One line per job: the value of runs-on. Steps, triggers, secrets, permissions, concurrency, caches and every other key stay exactly as they are, down to your indentation, comments and quote style. Your reviewer sees one changed line per migrated job and nothing else.
Which jobs does it move?
Every job on ubuntu-latest or ubuntu-24.04 with a plain runs-on label. Jobs that belong on other runners, such as Windows, macOS or arm64, stay where they are, and the pull request lists each one with its reason.
Do I need a GitHub organization to use Sengi?
No. Sengi runs personal and organization repositories alike. You need write access to the repository so the agent can push a branch.
Why use a prompt instead of editing the files myself?
It is the same edit, done thoroughly. The agent reads every workflow in the repository, not just the two you remember, moves every job that fits, and hands you one pull request with its reasoning attached. You review and merge it; reverting it puts you back where you started.
Cut your GitHub Actions bill by up to 74%.
Up to half GitHub’s price per minute, on 5 GHz bare metal. Builds finish up to 1.9× faster than on GitHub’s runners, so together you pay up to 74% less per build. Change one runs-on line, or give your coding agent one prompt and it opens the pull request for you.
3,000 free minutes a month, no card. See how switching works
