Cutting a release then had one manual step left: watch build.yml and dispatch "Deploy to galactus" by hand with the version. Chain it. build.yml gains a `deploy` job, `needs: build` and gated on refs/tags/v*, that dispatches deploy-galactus.yml against the tag with tag=<version> scope=app bootstrap=false skip_migrate=false. `needs` waits for both matrix legs, so api and web are both in the registry before prod pulls either — deploy-galactus.yml only pulls, and a half-pushed pair leaves prod running one new image and one old one. A dispatch rather than a `workflow_run:` trigger (which Gitea has supported since 1.24) because deploy-galactus.yml reads github.event.inputs.* in ten places; under workflow_run all of them are empty strings, so the deploy would run with no tag. The dispatch keeps that workflow's contract intact and keeps it hand-runnable, which is how rollbacks work. The dispatch is confirmed the same way release.yml confirms the build started: snapshot the existing deploy-galactus run ids first, then require a new one to appear. An accepted dispatch that creates no run is the failure mode that cost v1.0.3 its images, and a plain "is there a deploy run" check would be satisfied by the previous release. Kill switch: repo variable AUTO_DEPLOY_GALACTUS=false prints the manual command instead of deploying. Needs the existing RELEASE_TOKEN secret; preflight fails loudly and names the manual command if it is unset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>