Skip to content

fix: uninstall no longer disowns a context its own supervisor set - #471

Merged
zcsizmadia merged 1 commit into
mainfrom
fix-uninstall-context
Sep 21, 2026
Merged

zcsizmadia merged 1 commit into
mainfrom
fix-uninstall-context

Conversation

@zcsizmadia

Copy link
Copy Markdown
Collaborator

#468 is fixed

EngineSnapshotSaveAndList passed in 72s, down from 154s. The || true on the engine ping was the cause, exactly as the diagnostics said.

The suite then moved on to NothingLeftBehind — a stage that had also never run in CI:

the machine's `skrog` docker context changed:
before="" after="npipe:////./pipe/skrog-e2e-suite"
the suite must hand back a context it took from another install

Third bug in the chain, each one hidden behind the last. Same shape as the first: a custom --pipe not carried through Skrog's own records.

The bug

skrog install records DockerContextHost — normally docker_engine. A supervisor started with --pipe custom then re-points the same shared context at its own endpoint, and nothing writes that back to the manifest.

At uninstall, contextIsOurs compared the live endpoint against the install-time value, concluded another install owned it, and left a skrog context pointing at a pipe it was about to delete. Every later docker --context skrog then fails — on a machine that has just uninstalled Skrog.

Why the existing protection stays

#217 taught this function not to reach too far: removing a context belonging to a second install breaks the first, and the symptom (context "skrog": context not found) looks like Skrog is broken rather than like cleanup overreaching. That reasoning is right and is untouched.

It was simply also not reaching far enough. The context our own supervisor set is ours.

served-pipe answers it — the durable record of what this install last asked to serve (#429), written at supervisor start and deliberately not cleared on exit, which is exactly what makes it readable here after the supervisor is gone. The record added for restart turns out to be the missing fact for uninstall too.

Negative-controlled

Disabling the new branch:

--- FAIL: TestContextIsOursWhenOurSupervisorRepointedIt
--- PASS: TestContextIsNotOursWhenAnotherInstallTookIt     <- #217 still protected

Only the new test fails, and a second new test pins the other direction directly: an endpoint neither the install nor its supervisor set is still not ours.

One self-inflicted detour, recorded

The first version of these tests went in through a heredoc, which ate a backslash and turned \.\pipe\... into \.\pipe\.... DockerHostFor fell through to its generic branch and the test failed against correct code — I nearly went looking for a bug in the product.

Same trap as the binfmt registration lines in #462, and the reason those are pinned to LF and piped as bytes rather than interpolated.

Next

Acceptance suite on main again. If NothingLeftBehind passes, that is the whole suite green for the first time and #448 finally unblocks.

#468 is fixed -- EngineSnapshotSaveAndList passed in 72s, down from 154s --
and the suite moved on to NothingLeftBehind, a stage that had also never run
in CI:

  the machine's `skrog` docker context changed:
  before="" after="npipe:////./pipe/skrog-e2e-suite"
  the suite must hand back a context it took from another install

Third bug in the chain, same shape as the first: a custom --pipe not carried
through Skrog's own records.

`skrog install` records DockerContextHost -- normally docker_engine. A
supervisor started with `--pipe custom` then re-points the SAME shared
context at its own endpoint, and nothing writes that back to the manifest. At
uninstall, contextIsOurs compared the live endpoint against the install-time
value, concluded another install owned it, and left a `skrog` context
pointing at a pipe it was about to delete. Every later
`docker --context skrog` then fails, on a machine that just uninstalled
Skrog.

#217 taught this function not to reach too far -- removing a context that
belongs to a second install breaks the first. That protection is right and
stays. It was simply also not reaching far enough: the context our own
supervisor set is ours.

served-pipe answers it. It is the durable record of what this install last
asked to serve (#429), written at supervisor start and deliberately not
cleared on exit -- which is exactly what makes it readable here, after the
supervisor is gone. The record added for restart turns out to be the missing
fact for uninstall too.

Negative-controlled by disabling the new branch: only the new test fails,
and TestContextIsNotOursWhenAnotherInstallTookIt still passes, so #217 is
not being undone. A second new test pins that directly -- an endpoint
neither the install nor its supervisor set is still not ours.

One self-inflicted detour worth recording: the first version of these tests
was written through a heredoc, which ate a backslash and turned
`\.\pipe\...` into `\.\pipe\...`. DockerHostFor then fell through to its
generic branch and the test failed against correct code. Same trap as the
binfmt registration lines in #462, and the reason those are pinned to LF and
piped as bytes.
@zcsizmadia
zcsizmadia merged commit 1c6fdf8 into main Sep 21, 2026
5 checks passed
@zcsizmadia
zcsizmadia deleted the fix-uninstall-context branch September 21, 2026 23:37
zcsizmadia added a commit that referenced this pull request Sep 22, 2026
`skrog uninstall` removed the distro, the data dir, the manifest, the
autostart entry and the docker context, and left the always-on process
that serves the pipe running.

Nothing was stopping it, and nothing could have: on a real install the
supervisor is detached. `skrog start` spawns it and calls Process.Release,
autostart launches it at logon, skrogw.exe relaunches it after a crash —
uninstall is not its parent and never was. The only uninstall that ran
beside a live supervisor and still looked clean was the e2e suite's, which
kills its own child by handle first.

What survived served a pipe into a distro that had just been unregistered,
rewrote endpoint records into the state dir being emptied, could re-point
the shared `skrog` docker context the uninstall had just decided whether it
was allowed to remove (#217, #471), and held skrog.exe open — so on Windows
the directory Skrog was installed into could not be deleted, which is what
a package-manager uninstall does next.

"Removed. Nothing else on the system was modified." was printed over all of
it.

The mechanism already existed: `restart --supervisor` asks through the
request file, waits for the single-instance claim to drop, and relies on the
supervisor exiting 0 so skrogw treats it as asked-for rather than a crash.
Uninstall needs exactly that minus the replacement, so the wait is now
stopSupervisor, shared by both. It runs before the docker-context step —
a supervisor stopped after it could hand the context straight back — and a
supervisor that will not exit is a warning, not an aborted uninstall.
zcsizmadia added a commit that referenced this pull request Sep 22, 2026
#475)

* e2e: assert no supervisor outlives the install (#474)

The suite's only remaining failure is its own temp-dir cleanup:

  TempDir RemoveAll cleanup: unlinkat ...\001\skrog.exe: Access is denied.

A process is still running from the binary it built. It is the supervisor
`restart --supervisor` spawned: detached and released by `skrog start`, so
the suite has no handle to it, and nothing in `skrog uninstall` stops it.

NothingLeftBehind said nothing about it, because until that stage existed
the only supervisor the suite ever saw was its own child, killed by handle
before the uninstall ran. Every real install has the orphan shape from the
first launch, since autostart and skrogw.exe spawn it the same way.

This commit is the assertion alone, so it can be watched failing before
the fix lands.

* fix: uninstall stops the supervisor it leaves running (#474)

`skrog uninstall` removed the distro, the data dir, the manifest, the
autostart entry and the docker context, and left the always-on process
that serves the pipe running.

Nothing was stopping it, and nothing could have: on a real install the
supervisor is detached. `skrog start` spawns it and calls Process.Release,
autostart launches it at logon, skrogw.exe relaunches it after a crash —
uninstall is not its parent and never was. The only uninstall that ran
beside a live supervisor and still looked clean was the e2e suite's, which
kills its own child by handle first.

What survived served a pipe into a distro that had just been unregistered,
rewrote endpoint records into the state dir being emptied, could re-point
the shared `skrog` docker context the uninstall had just decided whether it
was allowed to remove (#217, #471), and held skrog.exe open — so on Windows
the directory Skrog was installed into could not be deleted, which is what
a package-manager uninstall does next.

"Removed. Nothing else on the system was modified." was printed over all of
it.

The mechanism already existed: `restart --supervisor` asks through the
request file, waits for the single-instance claim to drop, and relies on the
supervisor exiting 0 so skrogw treats it as asked-for rather than a crash.
Uninstall needs exactly that minus the replacement, so the wait is now
stopSupervisor, shared by both. It runs before the docker-context step —
a supervisor stopped after it could hand the context straight back — and a
supervisor that will not exit is a warning, not an aborted uninstall.

* ci: the nightly e2e schedule comes back on (#448)

The suite is green end to end on a hosted runner: run 35670620632,
--- PASS: TestAcceptance (334.46s), every stage passing or skipping, and
the skips are the optional tooling windows-latest does not carry.

#448's condition was "re-enable when the suite can be green", not "when
#429 is fixed" — the point of switching it off was that a nightly nobody
can read is worse than none. The condition is met rather than waived.

Back on 04:20 UTC, the cron #448 asked for.
zcsizmadia added a commit that referenced this pull request Sep 22, 2026
RELEASING.md asks for the entry in the same PR as the code, and six
user-facing changes went in without one. Five of them are the chain the
acceptance suite turned up once it actually ran, each hidden behind the
one before it, and #429 is listed in 0.6.0's Known issues as still broken:

  #429  restart --supervisor lost the pipe it was serving
  #468  a dead dockerd read as "cannot tell", so the supervisor stopped
        repairing it -- plus the restore diagnostics that found it
  #471  uninstall disowned a context its own supervisor had set
  #472  uninstall left the supervisor running, holding skrog.exe open
  #444  autostart failed on a profile with no Run key

The emulation entry also gains the fact a reader needs and it did not
carry: the emulator is IN the rootfs, so the feature wants engine
29.8.1-3 or newer, and an older image says `missing interpreter` and
runs without emulation rather than registering a handler pointing at
nothing.

Separately, site/home.md still told visitors code signing "is applied
for through the SignPath Foundation, with signing by SignPath.io". The
application was declined (#358). README.md, docs/install.md and
docs/code-signing.md all say so; the site landing page was the one file
left claiming otherwise, and it is the page the docs site opens on.

The heading stays [Unreleased]. Dating it is the last commit before the
tag, as #427 did for 0.6.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant