Repository navigation
fix(devenv): confirm a stopped edge from the container, not a new listing - #46
Merged
Merged
Conversation
…ting protect_for stopped the edge before a database change and then failed the command (exit 6) if a new docker compose ps still listed the edge. In one CI run ST-50 failed this way although the edge was not running right afterwards and the database was unchanged. The most likely cause, inferred from the Docker daemon's code and not reproduced, is that the container list behind docker compose ps can still show a container as running for a moment after docker compose stop returned. The stop's exit status and error output were discarded, so that case could not be told apart from a stop that really failed. - Whether the edge stopped is now checked on the containers listed before the stop, with docker container inspect (stopped or removed passes; still running, or any other inspect error, fails closed). - The error names docker compose stop's exit status and last error line. - A failing docker compose ps is no longer taken for "no edge": the command stops before the gate is recorded or the database changed. - U-23 covers a stale listing, a removed container, a failed stop with its error, a stop that returns 0 while the edge runs, an inspect error and a failing listing.
knittingcode
marked this pull request as ready for review
October 29, 2026 15:31
knittingcode
added a commit
that referenced
this pull request
Oct 29, 2026
knittingcode
added a commit
that referenced
this pull request
Oct 29, 2026
knittingcode
added a commit
that referenced
this pull request
Oct 29, 2026
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.
Summary
protect_forinbin/devenvstops the edge before any database change (role corrections, migrations, upgrade, password rotation). After the stop it now confirms that the edge stopped from the containers themselves (docker container inspect), instead of from a newdocker compose pslisting. Its error now names the exit status and last error line ofdocker compose stop. A failingdocker compose psis no longer taken to mean "no edge".Motivation and context
CI run 40836555626 (PR #37, head
cc2e11d, attempt 1) failed ST-50 "a verified rotation whose edge cannot get ready" with:The failure stayed safe: the database was not changed. But it was the wrong outcome. The only source of that message is
protect_for. It rancompose_op stop edge >/dev/null 2>&1without checking its exit status, then failed ifcompose ps --quiet edgestill listed the edge. Yet the test'sdocker ps, taken right afterwards, found no running edge. Nothing inbin/devenvstops the edge afterdie, and the edge has no restart policy. So the stop had taken effect. The whole rotation,make status, the api wait and a fullmake uptook 9.35 s, so the stop did not run into its 10 s grace period.The most likely cause is in the Docker daemon. When a container exits, the daemon wakes the waiting stop call first, so
docker compose stopreturns. Only afterwards does it update the container list thatdocker psanddocker compose psread. A listing taken in that window can still show the container as running.docker container inspectreads the container itself.This was inferred from the daemon's code, by an independent read-only review, and not reproduced. In 200 recent CI runs the message appears only in this run. The PR's own change (go-chi/chi 5.3.2 → 5.4.0 in the edge's Caddy build) was assessed separately as not plausibly involved:
Implementation
protect_for: it lists the edge containers once and fails (exit 6) if that listing fails. This happens before the gate is recorded and before the database changes. Afterdocker compose stopit checks those IDs withedges_stopped.stop's stderr goes to a temporary file (new_tmp); its last line, stripped of control characters and capped at 200 characters, is added to the error together with the exit status.edges_stopped(new):false) or "No such container/object" passes.The gate is still recorded before the stop, the call still runs in the parent shell (signal handling unchanged), and the success message is unchanged.
Testing performed
shellcheck -x bin/devenv tests/devenv/unit.sh: clean, the same as on main.tests/devenv/unit.sh, run locally inside the Docker isolation from test(ci): isolate the unit and guard suites from Docker #43 (scripts/ci/no-docker.sh, on a local, unpushed combination of this branch with test(ci): isolate the unit and guard suites from Docker #43):bin/devenv: 5 of the new U-23 checks fail, including the stale-listing case, which fails exactly as in CI (exit 6, "could not be stopped"). The removed-container case passes on both and serves as a control.docker eventsduring the runs: no events from any FlowCRM container or project.stopis not called).tests/app/stack.sh): run only in CI.Security considerations
The property is unchanged: no database change may start while an edge listed before the stop could still serve.
The error message carries only Docker's error text, sanitised and truncated. It contains no secrets.
Known limitations and follow-ups
docker compose up, is not detected. The old second listing would have caught one; this is outside the lock's threat model.docker, asedge_statealready does. If the two ever reached different daemons, "No such container" would pass.gate_failedandcancel_db_opstill treat a failedcompose psas "no edge". That is a separate follow-up.