Match OCI runtimes by runtime, not by spelling - #98
Merged
Conversation
`sandbox-cli run --runtime runc` is refused on Rocky Linux 10.2 — a host whose
default runtime is runc:
runtime "runc" is not registered with the Docker daemon
available runtimes: io.containerd.runc.v2
One daemon, two vocabularies for the same runtime, in one command:
$ docker info --format '{{.DefaultRuntime}}'
runc
$ docker info --format '{{json .Runtimes}}'
{"io.containerd.runc.v2":{"path":"runc", …
`.Runtimes` is keyed by the containerd **shim** name. Every list here is written
in runtime names, and parseRuntimeNames takes the JSON keys verbatim, so the two
were compared as plain strings and never matched.
runtimeName reduces `io.containerd.<runtime>.v<major>` to `<runtime>` and leaves
everything else alone; StrongerRuntime, notHostDefault and runtimeHint ask
through it. The raw name is still what gets displayed and handed back to the
engine, because the mapping only goes one way — a shim name yields one runtime
name, a runtime name does not say which shim major a host registered.
The failure directions were not equal, which is why this is worth more than the
error message it fixes. Unable to see `io.containerd.runc.v2`, the tool called
runc unavailable and flagged an ordinary run as being on an unusual runtime:
wrong labels. Unable to see `io.containerd.kata.v2`, it would not have counted a
registered Kata as a kernel of its own at all — a boundary reported absent while
installed, which is the opposite of the direction these lists are documented to
fail in.
Found by running phase 1/2 of #89 on a real Rocky host. Everything added is a
pure function of a name, so the matrix is tested without Kata or gVisor
installed — which matters, because the hosts that have them are the hosts nobody
has.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…metric
Three review findings on this PR, and the first is the same class of mistake it
was written to fix.
**Accepting a spelling is only safe if the spelling sent is one the engine
knows.** runtimeHint was loosened to match by runtime, but nothing wrote the
matched key back, so BuildArgs still handed docker the user's original string.
A request for io.containerd.runsc.v1 against a host registering v2 passed the
preflight and then died at launch with "unknown or invalid runtime name" — the
terse message this check exists to replace, now with the helpful one suppressed.
Under fleet or --detach that is `exit status 125` and nothing else.
resolveRuntime returns the engine's own key for the requested runtime and the
caller assigns it to spec.Runtime. Exact match first, normalised second: a
spelling the engine itself uses is the one to send.
**The preflight was inert under podman.** It asked `{{json .Runtimes}}`
directly, a field podman does not have, so .Output() errored and the check
returned nil on every podman run. It now goes through runtimeNames, which has
the podman branch doctor already uses — and does not *refuse* there, because
podman reports only the runtime it is using and absence from that list is not
evidence of absence from the host.
**Normalisation is right for one list and wrong for the other.** The two fail in
opposite directions by design, so normalising both was not symmetry, it was a
regression in one of them: an admin can point io.containerd.runc.v2 at
sysbox-runc, and folding every shim-shaped runc name into the shared-kernel set
suppressed the RUNTIME column for exactly the sessions it exists to reveal.
strongerRuntimes still normalises, because a shim name is still that runtime and
missing a real boundary is the failure that matters there. sharedKernelRuntimes
looks up raw, so a name this file has not seen literally is shown and not
characterised.
Amitgb14
added a commit
that referenced
this pull request
Aug 9, 2026
Merges main and fixes the one comparison in ClassifyRuntimeGap that still read names as strings. `containsRuntime` decides GapMissing, and GapMissing is a prod refusal. A containerd-backed daemon keys .Runtimes by shim name — `io.containerd.runc.v2` on Rocky Linux 10.2, in the same `docker info` that calls its default `runc` — so `--runtime kata` against a host listing `io.containerd.kata.v2` read as missing and prod refused a machine that had the kernel it was demanding. It now asks SameRuntime, which #98 added for exactly this and which the rest of this file already uses. The permissive direction is unchanged: it can only find a runtime the engine really listed, and a name no entry means is still GapMissing, so prod still refuses before the launch fails. This matters before #89 phase 3 rather than after it. That checklist walks the gate with `--runtime` values against a real daemon, and on a containerd host it would have been measuring a comparison already known to be wrong here — the same trap that would have had its `--runtime runc` case pass for the wrong reason. The merge conflict in isolation.go was two rewrites of notHostDefault's doc comment over an identical body: main's account of why normalisation is deliberately asymmetric between the two lists, and this branch's account of why both callers need the unrecognised-name direction. Both are kept.
This was referenced Aug 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found by running #89's phases 1–2 on a real Rocky Linux 10.2 host.
sandbox-cli run --runtime runcis refused there — on a machine whose defaultruntime is runc:
One daemon, two vocabularies for the same runtime, in one command:
.Runtimesis keyed by the containerd shim name. Every list inisolation.gois written in runtime names andparseRuntimeNamestakes theJSON keys verbatim, so the two were compared as plain strings and never matched.
The fix
runtimeNamereducesio.containerd.<runtime>.v<major>to<runtime>andleaves everything else alone.
StrongerRuntime,notHostDefaultandruntimeHintask through it.Reducing rather than expanding, because the mapping only goes one way: a shim
name yields exactly one runtime name, while a runtime name does not say which
shim major a host registered. So the raw name stays what gets displayed and
handed back to the engine — the refusal above still lists
io.containerd.runc.v2, since that is what the user will see indocker infoand must write in
daemon.json.The pattern is matched exactly rather than by splitting on dots: the prefix
alone is not enough, the
.v<digits>suffix has to be there, and the separatoris taken from the right. A runtime genuinely named with dots keeps its name.
Why it is worth more than the error message
The two failure directions were not equal.
Unable to see
io.containerd.runc.v2, the tool called runc unavailable andflagged an ordinary run as being on an unusual runtime — wrong labels.
Unable to see
io.containerd.kata.v2, it would not have counted a registeredKata as a kernel of its own at all — a boundary reported absent while
installed.
isolation.godocuments these lists as failing only in the safedirection ("nothing here can claim a boundary a run did not get"); a shim name
broke that, in the direction the comment rules out.
That second half is inference, not observation: this host has no Kata, and how a
given installer keys its
daemon.jsonentry is unverified. #89's phase 3 willanswer it for gVisor, since
runsc installwrites the entry itself. The fixcosts nothing either way and makes both lists mean what they say on a
containerd-backed daemon, which is now the ordinary case on RHEL-family hosts.
Testing
Every added function is pure, so the whole matrix runs on a machine with neither
Kata nor gVisor installed — which is the point, since the hosts that have them
are the hosts nobody has.
Note for #88
ClassifyRuntimeGaponfeat/prod-demands-a-kernelcomparescontainsRuntime(s.All, effective)by string too. It inheritsStrongerRuntimeand
notHostDefaultfrom this change when the branch next mergesmain, butthat one call wants
SameRuntimeas well — otherwise--runtime kataagainst adaemon listing
io.containerd.kata.v2reads asGapMissing.