Status: accepted — which branches reach the prompt is superseded by ADR 0008, which extends it to a stopped container and inverts the countdown there.
toolbox shell refreshes the runtime image against its registry on the way
in. The refresh is synchronous and cache-gated, so the cost lands unevenly:
on a cold cache the developer waits out a pull they never asked for, and on
a warm one they get nothing back for the wait they didn't have. Neither
outcome is wrong, and that is the problem — the shell decides, silently,
something the developer has an opinion about.
We make the shell ask, with a countdown that answers for a developer who
isn't looking. When the registry is ahead of the local store, a prompt offers
the download with a visible five-second countdown; y, a bare Return, or a
timeout all pull synchronously and start the session on the new image, and
n starts the session on the image already in the store while the background
prefetch fetches the new one. The countdown is visible rather than implicit
because five seconds of silence is indistinguishable from a hang, and a
developer who looks up to find the download already running should be able
to see why.
Three cases never reach the prompt, because in each of them the answer is
already settled. An image missing from the store entirely is pulled
synchronously and without asking — there is no session to start otherwise, so
the block is not a cost but the only honest thing to do. pull: always pulls
without asking, because a policy that has already said yes on every shell
cannot coherently be asked again. pull: never neither probes nor prompts,
since a probe is a registry round-trip and not probing is that policy's whole
promise. Without a tty there is no prompt either, and the default inverts:
start now, fetch behind. The interactive default is justified by the work
that follows the wait; a script has no work that follows, so the same wait is
pure latency multiplied by every invocation in a pipeline.
A container that already exists is not among these cases, and it is not settled by the same argument: the answer there is not already known, it is paid for differently — a yes costs the container as well as the wait. Which branches may be asked, and what the countdown may answer on each, is settled by ADR 0008.
Knowing whether to ask is itself a registry round-trip, so the question is
answered from the update prefetch's shared cache whenever its stamp is warm —
a sibling session that probed a moment ago has already established the fact,
and re-establishing it would reintroduce, one step higher, the latency this
decision is about. Only a cold stamp probes synchronously, and that is
precisely the case where the question is most likely to be worth asking: no
session has been open recently, so the store is probably behind. The probe is
a DistributionInspect — metadata, not a download.
Drop the synchronous refresh entirely and let the background prefetch own the registry, with the session adopting the new image at its next reload. It removes the wait completely and was our first choice. We rejected it because the first shell after a release then always starts behind, and the reload that would fix it is delayed by the very grace window that protects a session from being recreated moments after it opened. The mechanism designed to be unobtrusive would have been most obstructive exactly where it mattered.
Keep the current unconditional synchronous pull. Correct, and the status quo; it simply never lets the developer decline a download they have a reason to postpone.
Wait for the pull up to a bounded number of seconds, then continue in the background. A timeout instead of a question, which sounds like the best of both. Rejected: deciding whether to wait needs the synchronous probe anyway, and a pull of fresh layers almost never lands inside a seconds-scale bound — so the wait is paid and the session starts on the old image regardless.
A "no" is a postponement, not a refusal, and it says so by arming the
Idle Reload for that session even when
update.idle_reload is off. The alternative — a "no" that holds until the
session ends — would turn a five-minute deferral into a permanent one, which
is not what the developer was asked.
Nothing new downloads on the "no" path. The background prefetch already runs an immediate pass when a session opens, and it is already what advances the store; a second fetch started by the declined branch would mean two concurrent pulls of the same ref at the same moment.