Ubuntu 26 generally available
On September 17th, 2026 GitHub announced the ubuntu-latest Actions runner label will migrate from Ubuntu 24.04 to Ubuntu 26.04 starting on October 19, 2026 which is one week away.
“Because this moves your workflows to a new operating system version, the migration may break builds that rely on tools, packages, or versions that changed between Ubuntu 24.04 and 26.04.”
If you're on StarSling, just accept the PRs from our agent updating the runner labels to starsling-ubuntu-26.04. Full list of new Ubuntu 26.04 runner labels available in our docs.
If you're on GitHub-hosted runners and using the ubuntu-latest label, the update is being rolled out gradually through November 19, 2026, and can break builds. Use this agent prompt to check which of your jobs are affected.
What changed in the image
Comparing the runner-images software lists for Ubuntu 24.04 (image 20261004.327.1) against Ubuntu 26.04 (image 20260927.149.1), here are the changes:
| Tool | Ubuntu 24.04 | Ubuntu 26.04 |
|---|---|---|
| OS | 24.04.5 LTS | 26.04.1 LTS |
| Kernel | 6.17 | 7.0 |
| systemd | 255 | 259 |
| Python | 3.12.3 | 3.14.4 |
| Node.js | 22.23.3 | 24.21.0 |
| Java (default) | 17 | 25 |
| GCC / G++ | 12.4, 13.3, 14.2 | 13.4, 14.3, 15.2 |
| Clang | 16, 17, 18 | 20, 21, 22 |
| Ruby | 3.2.3 | 3.3.8 |
| PHP | 8.3.6 | 8.5.4 |
| Perl | 5.38.2 | 5.40.1 |
| OpenSSL | 3.0.13 | 3.5.5 |
| CMake | 3.31.6 | 4.4.3 |
| Maven | 3.10.0 | 3.9.15 |
| npm | 10.9.9 | 11.19.0 |
| pip | 24.0 | 25.1.1 |
| Docker | 28.0.4 | 29.4.2 |
| Docker Compose | 2.38.2 | 5.1.3 |
| Podman | 4.9.3 | 5.7.0 |
| Helm | 3.22.0 | 4.3.0 |
| PostgreSQL | 16.15 | 18.6 |
| MySQL | 8.0.46 | 8.4.11 |
Find the jobs Ubuntu 26.04 affects
Have your coding agent list every job on ubuntu-latest and flag the ones that use a tool removed or upgraded in 26.04:
Flag jobs affected by Ubuntu 26.04
List every GitHub Actions job in this repository that runs on ubuntu-latest. GitHub moves ubuntu-latest from Ubuntu 24.04 to Ubuntu 26.04 gradually between October 19 and November 19, 2026.
1. Read every workflow file under .github/workflows/ and every reusable workflow they call from this repository.
2. For each job, resolve its runs-on value, including matrix entries (for example os: [ubuntu-latest, macos-latest]) and expressions that evaluate to ubuntu-latest.
3. Print a table with one row per job on ubuntu-latest: workflow file, job id, line number, and the runs-on value as written.
4. For each of those jobs, list the preinstalled tools it calls directly (language runtimes, compilers, databases, CLIs) and the version it expects, if the workflow or repository pins one (setup-* steps, .nvmrc, .python-version, .tool-versions, Dockerfiles, lockfiles).
5. Flag every job that uses a tool on these lists, and say which change affects it.
Removed in Ubuntu 26.04: Julia, Swift, Miniconda, Java 8, Lerna, Fastlane, Newman, Parcel, Pulumi, Mercurial, MediaInfo, Haveged, Sphinx search server. p7zip-full is replaced by 7zip.
New default versions (Ubuntu 24.04 -> 26.04):
- Kernel 6.17 -> 7.0; systemd 255 -> 259
- Python 3.12.3 -> 3.14.4
- Node.js 22.23.3 -> 24.21.0
- Java default 17 -> 25
- GCC / G++ 12.4, 13.3, 14.2 -> 13.4, 14.3, 15.2
- Clang 16, 17, 18 -> 20, 21, 22
- Ruby 3.2.3 -> 3.3.8; PHP 8.3.6 -> 8.5.4; Perl 5.38.2 -> 5.40.1
- OpenSSL 3.0.13 -> 3.5.5
- CMake 3.31.6 -> 4.4.3; Maven 3.10.0 -> 3.9.15
- npm 10.9.9 -> 11.19.0; pip 24.0 -> 25.1.1
- Docker 28.0.4 -> 29.4.2; Docker Compose 2.38.2 -> 5.1.3; Podman 4.9.3 -> 5.7.0; Helm 3.22.0 -> 4.3.0
- PostgreSQL 16.15 -> 18.6; MySQL 8.0.46 -> 8.4.11
A job that relies on the preinstalled default and does not pin a version is affected by that tool's change. A job that pins that tool's version with a setup action is not.
These lists come from GitHub's runner-images software lists. For any preinstalled tool a job uses that is not listed above, compare its version in both:
- Ubuntu 24.04: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md
- Ubuntu 26.04: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2604-Readme.md
Do not edit any file. Print the table and the flagged jobs, then stop.Test on ubuntu-26.04
Set runs-on: ubuntu-26.04 (ubuntu-26.04-arm for arm64). To test both images side by side, use a matrix:
jobs:
test:
strategy:
matrix:
os: [ubuntu-24.04, ubuntu-26.04]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- run: make testWhen 26.04 passes, drop the matrix and use ubuntu-26.04 or ubuntu-latest.
Pin ubuntu-24.04
To stay on 24.04 while you test:
jobs:
build:
runs-on: ubuntu-24.04Switch to StarSling Runners
For faster, self-improving runners that handle migrations like this for you, migrate to StarSling Runners. Give your coding agent this prompt:
Migration prompt
# Migrate GitHub Actions to StarSling Runners
Migrate the user's workflows from GitHub-hosted runners to StarSling Runners.
**Prerequisites:** `gh` CLI authenticated, [StarSling GitHub App](https://github.com/apps/starslingdev) installed on the repo's org.
## Configuration
**Target:** `starsling-ubuntu-24.04` | **Branch:** `migrate-starsling-ubuntu-2404`
**Source runners to replace:** `ubuntu-latest`, `ubuntu-24.04`
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 GitHub App Installation
Ask the user: "Have you installed the [StarSling GitHub App](https://github.com/apps/starslingdev) on your org? It's required for runners to pick up jobs after merge. If not, please install it first and let me know when you're ready."
**Do not run any commands or proceed to Step 2 until the user explicitly confirms the app is installed.**
### Step 2: Verify CLI Auth
Verify `gh auth status` succeeds. If not, direct the user to install from https://cli.github.com/ and run `gh auth login`.
### Step 3: Get Repository
Ask the user for the repository (`owner/repo`). Then run `gh api repos/OWNER/REPO --jq '.owner.type'`. If the result is `User` (not `Organization`), stop and explain: "StarSling Runners only work with GitHub organization repositories. You can create a free organization at https://github.com/account/organizations/new."
### Step 4: Discover Workflows
Fetch all workflow files in one API 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
```
Note: `HEAD` in the GraphQL expression is a Git ref, not a placeholder - do not replace it.
If the response is truncated or errors due to size, fall back to the REST API: fetch the default branch and HEAD SHA via `gh api repos/OWNER/REPO --jq '.default_branch'` and `gh api repos/OWNER/REPO/git/ref/heads/DEFAULT_BRANCH --jq '.object.sha'`, then fetch workflow file names via `gh api repos/OWNER/REPO/contents/.github/workflows` and each file individually via `gh api repos/OWNER/REPO/contents/.github/workflows/FILE_NAME`. The response `content` field is base64-encoded - decode it before scanning for `runs-on:` values.
Save `defaultBranchRef.name` (or REST default branch), `.target.oid` / HEAD SHA, and workflow `entries[]`. Scan each `.yml`/`.yaml` file for:
- direct `runs-on:` string values matching supported runners, and
- matrix-driven patterns (e.g., `runs-on: ${{ matrix.os }}`) where the matrix values include supported runner labels.
Show the user a summary including:
- Workflows that will be migrated and which runners are being replaced
- Any Ubuntu runners NOT in the supported list (e.g., `ubuntu-22.04`, `ubuntu-20.04`, larger runners like `ubuntu-latest-16-cores`) listed as "Not migrated - unsupported runner label"
If no direct or matrix-backed supported runners match, stop.
### Step 5: Preview Changes
**Never create a PR without the user confirming changes first.**
Show what will change per workflow:
- Replace supported runners in `runs-on:` string values with `starsling-ubuntu-24.04` (preserve quotes/comments)
- Replace matching `runner:`/`os:` values in `matrix.include` sections
- Skip commented lines
**Complex `runs-on` patterns:**
- **Array syntax** (e.g., `runs-on: [self-hosted, linux, ubuntu-latest]`): Only replace the matching label within the array; do not collapse to a single string
- **Group/labels syntax** (e.g., `runs-on: { group: ..., labels: [...] }`): Skip and flag for manual review
- **Expressions** (`${{ matrix.os }}`, ternary/conditional): If `runs-on` uses `${{ matrix.os }}` and the matrix values are hardcoded runner labels, replace the labels in the matrix definition and flag the workflow for manual verification. If the matrix values come from other expressions, skip entirely and flag for manual review
**YAML fidelity:** Change ONLY the `runs-on` and matrix values. Preserve the original file exactly: same indentation, key ordering, comments, blank lines, and trailing newline. The PR diff should show only the runner label changes.
### Step 6: Create PR
**Branch:** Before creating, check for existing branches: `gh api repos/OWNER/REPO/git/matching-refs/heads/migrate-starsling-ubuntu-2404 --jq '.[].ref'`. If any exist, find the highest numeric suffix and increment by 1 (if none have a suffix, use `-2`). Then create: `gh api repos/OWNER/REPO/git/refs -f ref=refs/heads/BRANCH_NAME -f sha=HEAD_OID`.
**Atomic commit** - all files in one commit, contents base64-encoded:
```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 StarSling Runners",
"body": "Replaced runners per file:\n- file1.yml: ubuntu-latest → starsling-ubuntu-24.04\n- file2.yml: ubuntu-24.04 → starsling-ubuntu-24.04"
},
"expectedHeadOid": "HEAD_OID",
"fileChanges": {
"additions": [
{ "path": ".github/workflows/FILE_NAME", "contents": "BASE64_CONTENT" }
]
}
}
}
}
MUTATION
```
Verify the response contains a valid `commit.oid`. If the mutation returned an `expectedHeadOid` mismatch, re-fetch the HEAD SHA (Step 4) and retry the commit once. For any other errors, report and stop - do not create a PR against a failed commit.
**Commit message:** The headline should read `Migrate N CI workflows to StarSling Runners` where N is the count of modified workflow files (`.yml` + `.yaml`). List each file and the runner label(s) replaced in the body, as shown in the example above.
**PR** via `gh pr create --repo OWNER/REPO --head BRANCH_NAME --base DEFAULT_BRANCH --title "Migrate N CI workflows to StarSling Runners" --body "..."` with this body structure:
```
## Summary
Migrates CI workflows from GitHub-hosted runners to [StarSling Runners](https://docs.starsling.dev) for faster builds and AI-powered optimizations and fixes.
## Changes
- \`file1.yml\`: \`ubuntu-latest\` → \`starsling-ubuntu-24.04\`
- \`file2.yml\`: \`ubuntu-24.04\` → \`starsling-ubuntu-24.04\`
## Not Migrated
- \`file3.yml\`: Uses \`${{ matrix.os }}\` - requires manual review
(or "All workflows migrated successfully.")
## After Merging
Workflows will automatically run on StarSling Runners. Ensure the [StarSling GitHub App](https://github.com/apps/starslingdev) is installed with access to this repo.
```
### Step 7: Monitor (Optional)
Ask the user if they'd like help monitoring after merge. If yes, explain they can return after merging and you'll check with:
```bash
gh run list --repo OWNER/REPO --branch DEFAULT_BRANCH --limit 5 --json status,conclusion,name,createdAt
```
Look for runs created after the merge. If any show `queued` for more than 2 minutes, suggest checking the GitHub App installation and repo access at https://github.com/apps/starslingdev.
## Error Handling
| Error | Solution |
|-------|----------|
| `gh` not found or not logged in | Install from https://cli.github.com/, run `gh auth login` |
| 403 / insufficient permissions | User needs write access to the repository - check collaborator status or org role |
| App not installed | Install from https://github.com/apps/starslingdev |
| Personal repo (owner type `User`) | StarSling requires an org repo - create one at https://github.com/account/organizations/new |
| Repository not found | Check repo name and permissions |
| No workflows found | Ensure `.github/workflows/` exists |
| Runner not available after merge | Verify app has repo access at https://github.com/apps/starslingdev |
| Rate limit exceeded | Wait a few minutes and retry |
| 422 on branch creation | Branch exists - append a number suffix |
| `expectedHeadOid` mismatch | Re-fetch HEAD SHA and retry the commit |
| `${{ matrix.os }}` / complex `runs-on` | Require manual review - determined at runtime |
| GraphQL response truncated | Fall back to REST API for individual file fetches |Source: StarSling Runners
Keep reading