Repository navigation
fix(pod): refuse a runtime handler the venue cannot launch - #49
Conversation
The containerd capability row declares runtime_handlers [crun, krun, runc, nvidia] and engineDefaultRuntime names the FIRST one unconditionally, so every containerd launch argv (and the generated systemd unit's ExecStart) carried `--runtime crun` — even on a venue that has no crun. There the unit restart-loops, the failed attempt leaves the container CREATED with its name held, and every retry dies `name-store error: name ... already used` (opencharly/plugin-containerd#16). Nothing consulted the VENUE. This change does: venueRuntimeHandlers() asks the same resolution the launch performs (exec.LookPath over the runtime executable, mirroring nerdctl's generateRuntimeCOpts and containerd's pkg/shim.BinaryName), and ensureVenueRuntimeHandler() refuses a config/start step whose selected handler the venue cannot launch — naming the selected handler, the handlers the venue reports, and the remedy (install the crun runtime; layer-containerd installs crun and krun). It never falls back to a different handler: a silent switch to another OCI runtime is a different substrate (R3). An engine with an empty runtime_handlers list (podman, docker) takes exactly the path it took before. A failed start now also cleans up after itself: the unit is stopped (so Restart=always cannot re-create the container) and the partial container is removed through the SAME teardown argv `charly remove` uses (engineRemoveArgv), so a retry starts from a free name. The direct-mode config launch applies the same removal on failure. MEASURED on this host's rootless containerd 2.3.5 (nerdctl 2.4.1): /usr/bin/crun is absent; `nerdctl run --runtime crun` fails `failed to resolve runtime path: invalid runtime name crun`; `--runtime runc` succeeds. The repo's R10 bed could not complete on this host: its pinned image is anonymously denied by ghcr.io (filed as plugin-deploy-pod#48), reproduced on unmodified origin/main. Refs opencharly/plugin-containerd#16 Assisted-by: Claude Code deepseek-v4.1-flash:cloud (analysed on a live system)
Review — PASSHead SHA: Security screen: PASS — no scope mismatch, secrets, weakened guardrails, new deps, or embedded directives; the change is confined to the pod-deploy candy and matches its stated intent. Checklist: All applicable rules PASS or NA. The change ships unit coverage that fails without it ( Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
Summary
charly configfor apod:deploy onengine: containerdemitted a systemd unit whoseExecStartnamed a runtime handler chosen from the CAPABILITY ROW alone(
engineDefaultRuntime→RuntimeHandlers[0]→crun) and never asked the VENUE whether itcan launch it. On a venue whose containerd has no
crun, the daemon rejects the unit, systemdrestart-loops, the failed attempt leaves the container
CREATEDwith its name held, and everyretry dies
name-store error: name ... already used(opencharly/plugin-containerd#16).This change consults the venue:
venueRuntimeHandlers()(candy/plugin-deploy-pod/engine_ops.go) reports the subset of theengine's DECLARED handlers this venue can launch, asking the SAME resolution the launch
performs:
exec.LookPathover the runtime executable, mirroring nerdctl'sgenerateRuntimeCOpts(a bare word is the OCI-runtime binary) and containerd'spkg/shim.BinaryName(a runtime type iscontainerd-shim-<second-last>-<last>).ensureVenueRuntimeHandler()refuses the config/start step when the selected handler is notin that set, naming (a) the handler charly selected, (b) the handlers the venue reports, and
(c) the remedy: install the runtime on the venue (
layer-containerdinstalls crun and krun)or point the deploy at a venue that has it. It refuses instead of falling back to another
handler — a silent switch to a different OCI runtime is a different substrate (R3).
runPodConfigSetup(before any file is written or image work runs), atrunConfigDirect's launch, and atresolvePodStartDirect(start). An engine with an emptyruntime_handlerslist (podman, docker) takes exactly today's path: no probe, no--runtime, no new failure mode.podContainerStartstops the unit (soRestart=alwayscannotre-create the container) and removes the partial container through the SAME teardown argv
charly removeuses (engineRemoveArgv);runConfigDirectremoves it on a failed launchtoo.
resolvePodStartPlannow returns the selection it built the plan from, so the cleanupaddresses the same address/namespace the launch used.
Measured, not assumed: on this host (rootless containerd 2.3.5, nerdctl 2.4.1)
nerdctl infocarries no
Runtimesmap in either mode; the resolution nerdctl actually performs for a bare--runtimeword isexec.LookPath, which is what the probe mirrors.The second copy of the default (
plugin-containerd/candy/plugin-containerd/plugin.go,defaultRuntimeHandler = "crun") is not on this code path and is not changed here; it istracked by the same issue #16, which now carries this PR's evidence.
Refs opencharly/plugin-containerd#16 — the issue lives in another repository and is NOT closed
by this PR; this PR fixes the pod substrate's consumer side of it.
How tested
Go gate and structural validate on the committed head (
038d0323b553c3568a342966c162740ba2ae33c0,tree
7079076c80ccb952f4833d43e8553c09d11d6a26):The venue fact the fix is built on, measured live:
The changed path executed LIVE on this venue — the config step refuses before
ensure-imageis reached, naming the selected handler, the observed set and the remedy:
The unmodified base proceeds instead — it never consults the venue and reaches
ensure-image,where the bed's pinned image is denied by ghcr.io (see
## Filed issues):Negative control.
TestRunPodConfigSetup_RefusesHandlerAbsentFromVenuefails on theunmodified base and passes on this head. On the base I applied that single test plus its PATH
helper (the other new tests reference symbols that do not exist there) and ran it:
R10 bed — run, and it did NOT pass.
charly check run check-pod-ensure-gate(the repo's owndisposable: truebed) ran on this head; itspe-config-path-completesstep failed inensure-imagebecause the bed's pinned image is anonymously denied by ghcr.io, and the SAMEfailure reproduces on unmodified
origin/main(evidence in## Filed issues). The run's ownartifact says:
No R10 pass is claimed: the bed failed in setup on an artifact this change does not touch, so
it judges nothing about this diff. The attribution tier is lowered accordingly.
Rulebook compliance
AGENTS.md, this repo'sAGENTS.md,/charly-internals:git-workflow,/charly-internals:strict-policy,/charly-internals:root-cause-analyzer,/charly-check:check.bed's pinned image is denied (403
DENIED), reproduced on unmodifiedorigin/main, filedas check-pod-ensure-gate cannot run: its pinned 'public' ghcr.io image is anonymously denied (403) #48. Go gate and
charly box validatereport 0 warnings.venueRuntimeHandlers/ensureVenueRuntimeHandler) usedat every config/start site; the cleanup reuses
engineRemoveArgvandpodContainerStop, nosecond teardown.
another OCI runtime; no ad-hoc engine command was added to the product.
changed.
adds no transitional path.
git statusclean before and after; work in.worktrees/containerd-crabbox-program/rt16/plugin-deploy-pod; no destructive git, noforce-push, PR-only.
live on the venue (the pasted refusal).
nerdctlruntime measurements arereal.
./charly/scripts/bootstrap-charly.sh;charly versionprints2026.309.1009, equal togit -C charly describe --tags --abbrev=0(
v2026.309.1009); no new runtime OS dependency.externally denied image (reproduced on
origin/main, filed check-pod-ensure-gate cannot run: its pinned 'public' ghcr.io image is anonymously denied (403) #48); the tier is lowered ratherthan inflated.
Change classification
poddeploy substrate (config emission, directlaunch, start cleanup). No schema, flag, wire, or pin change;
charly box validatestillchecks the same candy and deploys.
now fails
charly config/charly startwith the selected handler, the venue's handlers andthe remedy, instead of writing a unit that can never start; a venue that has the handler
produces the same argv as before.
deliberate behavior change is the loud refusal on a venue that cannot launch the handler
(previously a unit that restart-looped and poisoned the container name). Rollback: revert
this commit — no state, schema, volume, or generated artifact migrates, and any unit already
written stays exactly as it is.
Filed issues
check-pod-ensure-gatecannot run on a host withoutGHCR credentials: its pinned "public" image is anonymously denied (403), reproduced on
unmodified
origin/main; this is why the R10 bed above is red in setup.consumer side is fixed here, and the engine provider's own unconditional
defaultRuntimeHandler = "crun"copy is tracked there (not on this code path, not changedhere).
Agent:
containerd-crabbox-program/rt16· session8b1c01cf-7f79-4a75-bff8-240415a06208Assisted-by: Claude Code deepseek-v4.1-flash:cloud (analysed on a live system)