Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The operational documentation incorrectly describes how an expected supervisorctl stop error affects the command’s exit status.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a safe operator workflow for rerunning side-car schema steps without stale sentinels releasing API gates.
Changes:
- Adds validated sentinel clearing and container packaging.
- Documents the rerun and Celery coordination procedure.
- Adds comprehensive tests and CI coverage.
File summaries
| File | Description |
|---|---|
sidecar/clear_sentinels.sh |
Validates schema steps and removes their sentinels. |
sidecar/README.md |
Documents the supported rerun procedure. |
sidecar/Containerfile.sidecar |
Installs the executable helper. |
sidecar/entrypoint.sh |
References the new manual workflow. |
sidecar/supervisord.conf |
Clarifies restart sentinel handling. |
.github/workflows/ci.yml |
Adds CI triggering and image smoke coverage. |
changelog.d/SEP-1970.added.md |
Records the operator-facing feature. |
tests/sidecar/conftest.py |
Centralizes shared side-car test constants. |
tests/sidecar/test_clear_sentinels.py |
Tests validation, removal, gating, and packaging. |
tests/sidecar/test_entrypoint.py |
Reuses shared constants. |
tests/sidecar/test_wait_for_api.py |
Reuses the shared Containerfile path. |
tests/sidecar/test_wait_for_schema.py |
Reuses shared schema-gate constants. |
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The constant moved into the shared conftest so test_clear_sentinels.py could consume it, but its docstring still named only the case that outlives the budget. Two of the three rewrite sites set it to 0 so the gate gives up on its first poll, including the new module's only use.
Automated QA — PASSBuilt the side-car image from the PR head commit and ran it against a real Postgres + PMM instance, then exercised the documented "re-running a schema step inside a running container" sequence directly:
|



Summary
Adds
sidecar/clear_sentinels.sh, an operator-invoked script that invalidates named schema steps' sentinels ahead of asupervisorctlre-run, and documents the one supported sequence for re-applying a migration inside a running container.entrypoint.sh(PID 1) clears all four/tmp/migrate-<step>.okmarkers beforeexec supervisord, which covers every container start and restart. Asupervisorctl restartnever re-enters PID 1: each one-shot removes its own marker only as the first word of its ownsh -ccommand, i.e. after it has been spawned, concurrently with a restarted API'swait_for_schema.shgate. The gate could therefore observe the previous run's marker and release its app against a schema this run has not re-applied. (Inferred from code inspection — not reproduced or QA-observed.)sidecar/clear_sentinels.sh(new, mode100755; copied into the image's app directory besidewait_for_schema.shwith--chmod=550, as the gate is). Takes one or more bare schema-step names, the same conventionwait_for_schema.shtakes. It validates every argument against its ownSCHEMA_STEPSlist before removing anything, then removes each named marker and prints what it cleared. An unrecognized name exits 2 having removed nothing; an empty argument list exits 2 with ausage:line. Names are matched whole, so a program name (migrate-sep), a two-word argument ("sep inventory"), an empty argument and a path-like one (../sep) are all refused before any path is built.sidecar/README.mdgains a "Re-running a schema step inside a running container" subsection carrying the sequence (clear the step's sentinel, then restart the one-shot together with the API programs), what the clear's exit status obliges the operator to do — including the recovery when anrmfails partway — why the container reads unhealthy until the step republishes, and whichsupervisorctloutput is expected noise (and whysupervisorctl's own exit status is not the verdict either: the start half faults on the veryERROR (abnormal termination)line the operator is told to ignore). It also documents Celery separately:celery-workercarries no schema gate andcelery-beatis gated only on the APIs answering, so when the re-run step owns tables Celery reads, both are stopped before the clear and started after the sentinel returns — naming them in the restart call would start them roughly 24s in (supervisord flips a gated API toRUNNINGonce itsstartsecselapses, while its shell is still inside the gate) and the ungated worker would consume tasks against the incomplete schema. Also adds the script to the "What it contains" table and qualifies the Health paragraph that previously said flatly that "a restart cannot release a gate on the previous run's markers" — true of a container restart, not of asupervisorctlone..github/workflows/ci.ymladdssidecar/clear_sentinels.shto thepython:path filter, and an executable-bit assertion for it to the image smoke test. The filter already enumerates the other six sidecar files the sidecar suite asserts on, precisely because a skipped job counts as green inci-success; without the entry, a future PR editing only this shell file would merge with the 18 new tests unrun.sidecar/entrypoint.shandsidecar/supervisord.confno longer state that thesupervisorctlpath merely races; both now name the script and the documented sequence.tests/sidecar/test_clear_sentinels.py(new, 18 cases) runs the script directly against a uuid-prefixed copy of itself: argument validation, subset clearing, idempotence, a repeated step, anrmthat cannot succeed, the imageCOPYand its mode, and a control/experiment pair showing that a stale sentinel releases a restarted gate while a cleared one holds it on exactly that step.tests/sidecar/conftest.pynow owns the constants the sidecar test modules shared by hand (CONTAINERFILE,ENTRYPOINT,GATE,SCHEMA_STEPS,SENTINEL_PREFIX,BUDGET_ASSIGNMENT);test_entrypoint.py,test_wait_for_api.pyandtest_wait_for_schema.pyimport them instead of redeclaring them. Imports only, plus one fixture docstring intest_wait_for_schema.pywhose enumeration of where the sentinel path is hardcoded would otherwise have become incomplete — no test body changed.PID 1's clearing is unchanged.
entrypoint.sh'srm -flines are byte-identical and nosupervisord.confcommand=line was touched; the only edits to those two files are comments. The change is otherwise additive: a new file in the image, new documentation, and new tests. No settings, API, schema or task payload is affected.Deviation from the ticket's Implementation Notes
The notes (explicitly "suggestions … not requirements") proposed extracting the sentinel step list into one place both
entrypoint.shand the new script could read. This ships the script with its own literal list plus a test pinning it to the supervisord program table instead — the same guard every other step-list site in the side-car already has (healthcheck.sh's list, the three gate invocations, and the entrypoint's own four paths are each pinned this way). A shared list could not actually be one place: the sevensupervisord.confsites aresh -c '…'strings inside an ini file the ticket marks unchanged, so extracting would unify two or three of nine sites while the rest stayed test-guarded anyway, and it would put a second shipped file on PID 1'serrexitpath. This was put to the maintainer as a four-way choice during planning and this option was selected.Tested
Automated coverage lives in
tests/sidecar/test_clear_sentinels.pyand is not repeated here. The scenarios below need a running side-car container, which could not be built in the implementing environment, so they are described rather than spelled out as command lines — the exact commands are in the "Re-running a schema step inside a running container" section ofsidecar/README.md.sepstep through the new script exits 0, reports the step it cleared, and leaves the other three markers in/tmpuntouched.migrate-sepinstead of the bare step name exits non-zero, names the accepted step names, and removes no marker.sepand restartingmigrate-septogether with the three API programs, the stop half reportsmigrate-sep: ERROR (not running)for the already-exited one-shot without changing the command's exit status;sep's sentinel reappears once the step finishes, and all three/healthendpoints answer 200.sepsentinel is absent the container reports unhealthy, and it returns to healthy once the step republishes the marker.Known minor issues
/tmp(uuid-scoped:/tmp/migrate-<uuid>-<step>.ok) rather than undertmp_path. Isolation is airtight regardless —prefixed_copyasserts the shipped script contains exactly one/tmp/migrate-occurrence before substituting, so a copy can never retain the real prefix, and teardown only unlinks the four known paths under its own prefix. Moving totmp_pathwould remove the shared-namespace dependency but changes nothing about correctness, so it was left as the plan specified.Checklist
make makemigrations) — N/A, no models changed