fix(sessionservice): close the spawn/remap race; surface container failures#319
Open
dkrattiger wants to merge 2 commits into
Open
fix(sessionservice): close the spawn/remap race; surface container failures#319dkrattiger wants to merge 2 commits into
dkrattiger wants to merge 2 commits into
Conversation
…ilures Two related reliability fixes for task-container spawns, both diagnosed live under load (~26 concurrent tasks): 1. The spawn race: `docker run --detach` returns at PID-1 start, not after the entrypoint's usermod/chown remap, so the tmux pane's `docker exec --user panopticon` could resolve the user to the pre-remap uid and die on a PermissionError writing the (post-remap-owned) home dir. The entrypoint now touches /run/panopticon-ready once the remap is done, and the pane command is a bounded host-shell wait for that marker (150 x 0.2 s, then proceeds so pre-marker images still launch) before exec'ing in. The guard lives in the pane command itself, so heal respawns, dashboard `R`, and a manual `respawn-pane` are all covered. 2. Failure surfacing: a container the kernel OOM-killed (or that exited nonzero) read as a bare `down` -- or `live` -- with the reason only in `docker inspect`. `LocalRunner.exit_reason` now asks the stopped container why (OOMKilled checked before the exit code, which an OOM kill can leave at a deceptively clean 0); `Spawner.reconcile` reports it as a `failed` lifecycle with that detail, including for an already-`down` task. The heal crash-loop cap is likewise reported as `failed` with a press-R pointer -- once, instead of a log line every pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tildesrc
approved these changes
Jul 14, 2026
…and-failure-surfacing # Conflicts: # src/panopticon/sessionservice/local_runner.py # src/panopticon/sessionservice/spawner.py # tests/sessionservice/test_local_runner.py # tests/test_spawner.py
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.
What
Two related reliability fixes for task-container spawns, both diagnosed live under load (~26 concurrent tasks):
1. The spawn/remap race (tasks failing to start)
docker run --detachreturns at PID-1 start — not after the entrypoint's usermod/chown remap (the old comment inlocal_runner.pyclaimed otherwise). Under load, the tmux pane'sdocker exec --user panopticonfrequently ran first, resolved the user to the pre-remap uid, and died:The dead pane took the session with it, heal respawned, the respawn lost the same race, and the churn ran until the crash-loop cap — tasks perpetually "failing to start".
Fix: the entrypoint touches
/run/panopticon-readyafter the remap, just beforeexec gosu; the pane command is now a bounded host-shell wait for that marker (150 × 0.2 s, then proceeds so pre-marker images still launch) before exec'ing in. Because the guard is the pane command itself, spawner, heal, dashboardR, and manualrespawn-paneare all covered.2. Container failures were invisible
An OOM-killed container (the burst trigger for the churn above —
docker inspectshowsOOMKilled=truewith a deceptively clean exit 0 and empty logs) read as a baredown, and the heal crash-loop cap only logged to the runner session.Fix:
LocalRunner.exit_reason— adocker inspectprobe of a stopped container (OOMKilled checked before the exit code).Spawner.reconcilereportsfailed+ that reason (e.g.container OOM-killed (exit 0)) instead of clearing to an unexplaineddown, including for a task that went down after being live.failed — keeps losing its tmux session (5 respawns) — press R to respawn, once, instead of an error log every pass.The dashboard's detail pane already renders
container: failed — <detail>; it just never had these details to show.Testing
docker inspectprobe, OOM-over-exit-code precedence, reconcile surfacing (in-flight,down, and the no-reason/no-churn case), the report-once cap, and the new pane command (wait loop + bounded fallback + exec).test_spawn_and_stop_real_container_and_session): with the rebuilt image the pane execs as soon as the marker lands (~11 s total, vs. burning the 30 s fallback against a pre-marker image).Notes
make buildon hosts after merge (entrypoint change ships in the base image; composed images rebuild automatically via the changed FROM layer).--continuefallback, sticky crash-loop cap, bootstrap-exception lifecycle reporting — can be re-scoped or dropped.🤖 Generated with Claude Code