Short version: ubuntu-latest is moving to Ubuntu 26.04 in percentage stages between 19 October and 19 November 2026, tracked in actions/runner-images#14748. The published change table says the Java default stays at 17, while images/ubuntu/toolsets/toolset-2604.json in the same repository declares "default": "25", and that JSON file is what builds the image. Set runs-on: ubuntu-24.04 to stop the clock, then diff the two toolset files.

One naming point first, because the rest of this depends on it: there are two descriptions of the 26.04 image, and they do not match. The 17 September changelog and announcement issue #14747 carry an eleven-row table of "selected" changes and record "Possible impact: N/A". The image's own Ubuntu2604-Readme.md and toolset-2604.json are generated from the build. Where they disagree, the toolset file wins, and an assistant trained on the announcement will tell you Java 17 held.

This is aimed at whoever owns a repo estate rather than a repo. Six days remain in the window, and the useful question is not whether 26.04 is any good. It is which of your pipelines have already crossed, which release artifacts were compiled against a different JDK, and which of last week's red builds were real. I keep a note on the previous floating-label move, node:lts on 28 October.

Job failed or passed this weekWhich image ran it?Treat as the new baselineUntested: pin, then matrix bothFailure, or silent version change?Check compilers, AppArmor dmesg, missing toolsRe-check java, python3, CONDA"imagedata.json says 2604""says 2404""Failure""Silent"

12 checks before the label flips on 19 November

  1. Diff the toolset JSON, not the announcement table. The table lists what GitHub chose to mention. The JSON is the build input, and the diff takes one command.
   base=https://raw.githubusercontent.com/actions/runner-images/main/images/ubuntu/toolsets
   diff <(curl -s $base/toolset-2404.json | jq -S .) <(curl -s $base/toolset-2604.json | jq -S .)
  1. Assume the default JDK moved two LTS majors. toolset-2604.json declares "java": {"default": "25", "versions": ["11", "17", "21", "25"]}, and the 26.04 readme lists 25.0.5+8 as default with JAVA_HOME_25_X64. A bare javac now emits class file major version 69, which a Java 21 runtime refuses to load, and Java 8 is absent from the image entirely.
   java -version 2>&1 | head -1
   javap -v -cp build/classes com/acme/Main.class | grep major
  1. Grep your build files for version-suffixed compiler binaries. The readme lists GNU C++ at 13.4.0, 14.3.0 and 15.2.0, and Clang at 20.1.8, 21.1.8 and 22.1.2 with default_version: 21. gcc-12, gfortran-12, clang-16 and clang-17 are gone, so a Makefile carrying CC=gcc-12 dies at exec rather than at configure. Cached native addons built on the old image are the second failure behind it: npm rebuild fixes half.
   grep -rnE 'gcc-1[0-2]|clang-1[0-9]|gfortran-1[0-2]' --include='*.yml' --include='Makefile*' .
  1. Four tools vanished that appear in no official list. Mercurial, Julia, Miniconda and Pulumi are missing from the 26.04 readme, and the public acknowledgement sits in issue comments: er0k wrote on 9 November that "It looks Julia has been removed entirely from the ubuntu 26 image", and UserArham added on 11 November that Miniconda went with it. The unpleasant case is the surviving-but-empty CONDA variable, which silently turns $CONDA/bin/conda into /bin/conda.
   printenv CONDA | wc -c
   command -v hg julia conda pulumi || echo "absent on this image"
  1. Log which image each job ran on, from both the environment and the disk. Hosted runners export ImageOS and ImageVersion, and the image also ships /imagegeneration/imagedata.json. Those two can disagree mid-rollout: #15201 reports the file reading 20261025.346.1 while the environment variable read 20261026.347.1. Action runtimes drift under a fixed label as well: node20 now runs on Node 24.
   - run: echo "$ImageOS $ImageVersion"; cat /imagegeneration/imagedata.json
  1. Run both images for the six days that are left. A two-entry matrix turns a nondeterministic label into two labelled results, which separates a 26.04 regression from an ordinary flake while you can still compare them.
   strategy:
     matrix:
       os: [ubuntu-24.04, ubuntu-26.04]
   runs-on: ${{ matrix.os }}
  1. Read the AppArmor sysctl that flipped between releases. kernel.apparmor_restrict_unprivileged_unconfined is 0 on 24.04 and 1 on 26.04. With it on, an unprivileged unconfined process that creates a user namespace runs under a stacked label, and the default container profile then denies signals inside the container. Your container hangs, your tool prints nothing, and the kernel ring buffer holds the only record.
   sysctl kernel.apparmor_restrict_unprivileged_unconfined
   sudo dmesg | grep 'apparmor="DENIED"'
  1. Expect enforce-mode profiles to deny file reads your tool never reports. On 11 November rstevens011 reported on #14748 that AppArmor on 26.04 blocks tshark from loading custom dissector plugins, filed as Launchpad bug 2173933. The workaround posted the following day is a local profile fragment, and it applies to any packaged binary that reads from $HOME.
   sudo aa-status | head -3
   echo 'owner @{HOME}/** mr,' | sudo tee /etc/apparmor.d/local/tshark
   sudo apparmor_parser -r -W /etc/apparmor.d/tshark
  1. The system interpreter is Python 3.14.4, and the toolcache declares no default. toolset-2604.json caches 3.11 through 3.15 with no default key, while the readme reports system Python as 3.14.4. Anything calling bare python3 to build a C extension is resolving wheels for a release many projects have not published yet, so pin it with actions/setup-python and an exact python-version instead of inheriting whatever the image ships.
  2. Inventory the label across the estate before fixing anything. Code search answers it in one call, with two blind spots worth writing down: it indexes default branches only, and a label buried in a reusable workflow or composite action will not match.
    gh api -X GET search/code -f q='"runs-on: ubuntu-latest" org:ACME path:.github/workflows' --jq .total_count
  1. Pin ubuntu-24.04, then price the deferral honestly. This row is unverified: neither the 17 September changelog nor #14748 publishes a retirement date for the 24.04 image, and the issue offers only "Switch back to Ubuntu 24.04 by specifying the ubuntu-24.04 label". The thing that settles it is a subscription to the Announcement label in actions/runner-images, not an assumption that a pin lasts a year.
  2. Confirm the architecture scope before migrating arm64 runners. The changelog describes GA "on both x64 and arm64"; #14748 scopes the label move to "the x64 ubuntu-latest label". Read the issue body yourself before you touch an arm workflow, because the opt-in label is probably ubuntu-26.04-arm.
    gh api repos/actions/runner-images/issues/14748 --jq .body | grep -i arm

FAQ

Will my ubuntu-latest jobs move without a commit from me? Yes. #14748 describes a percentage-based rollout from 19 October to 19 November 2026, so an unchanged workflow file can run on 24.04 one day and 26.04 the next.

Is Java 17 still available? Yes, as an explicit actions/setup-java version. toolset-2604.json lists 11, 17, 21 and 25 with 25 as the default. Java 8 is not listed.

Can I keep ubuntu-24.04 after 19 November? The label works and the issue names it as the fallback, with no published retirement date for the image. Watch the Announcement label rather than planning around a date nobody has given.

Why did a passing job start failing with no diff? Compare ImageVersion between the two runs before debugging your code. A 2404 to 2604 change means a different compiler set, a different default JDK and AppArmor restriction switched on.

Wrap-up

Probe the image, do not read about it. Every check here resolves to something the runner itself prints, which matters because the published table was accurate on 17 September and has since drifted on Java, Buildx, Rust, Firefox and three cloud CLIs. Pinning is one line per workflow; reconciling an estate, re-establishing which shipped binaries were compiled against JDK 25, and writing the AppArmor profiles a 26.04 fleet needs is scoped work with a real number attached, and 19 November does not move for it. Start with the sweep, commit it on a branch, and watch what goes red:

grep -rln 'runs-on: ubuntu-latest' .github/workflows | xargs sed -i 's/ubuntu-latest/ubuntu-24.04/'

What I read while writing this, in the order it was useful: the GitHub changelog for Ubuntu 26 GA, the migration tracking issue #14748 and its comment thread, the announcement issue #14747 with the eleven-row table, toolset-2604.json, Ubuntu2604-Readme.md, and the image-version mismatch report in #15201.