Repository navigation
ci(release): run on push to main and deploy the released head - #153
Conversation
The release now runs on every push to main as well as on dispatch, and a release that succeeds deploys the commit it just tagged. A dispatch gets a boolean `deploy` input, default true; unticking it publishes the image without deploying it. Owner decision 2026-11-03 (ADR 0024): every deploy needed the user, because Claude Code's auto mode refuses an agent's deploy dispatch. Shape: deploy.yml gains `on: workflow_call` and release-image.yml calls it as a job after `release`. A local reusable workflow runs "from the same commit as the caller workflow", and under workflow_call GITHUB_SHA is "Same as the caller workflow", so the unchanged resolve step reads image/<sha7> of the released commit, never :release. Build and deploy stay separate jobs, so "cannot be made" and "cannot go live" are still two red marks. Secrets are passed one by one, not `secrets: inherit`. The deploy condition is (push || inputs.deploy) && current == 'true' && attempt == run_attempt. On a push the `deploy` input does not exist and evaluates to an empty string, which GitHub's loose comparison equates with false, so `inputs.deploy != false` would skip every push; the tests model the documented comparison rules and pin that pitfall. A `current` job between the two reads `git ls-remote origin $GITHUB_REF` and deploys only while the released commit is still the head of its ref (owner decision 2026-11-03). A re-run keeps its original GITHUB_SHA and GitHub's concurrency queue says ordering is not guaranteed, so either could otherwise put an older commit over a newer one. A moved ref skips the deploy with a notice naming both shas and stays green; a failed read fails red. The attempt term stops "re-run failed jobs" from reusing an answer given while the old commit was still the head. deploy.yml's concurrency moves to its job, same group, not cancelled. That it holds for the call is unverified until a real run. The "timing is chosen by a person" reason is gone: the hourly cron is not avoided. No push filter: every push to main runs. Rejected: workflow_run (GITHUB_SHA is the default branch's head, not the released commit); an ancestry check (an older commit is always an ancestor, so it stops nothing); the guard inside deploy.yml (it would also gate a manual pin by digest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage 2's note read `gh run list --workflow deploy.yml`. A release now deploys by calling deploy.yml as a job of its own run, and a called workflow has no run of its own, so the note would show only the last deploy started by hand. production_last_deploy reads the last 10 runs of both workflows and keeps a run only if a job named `deploy` or ending in `/ deploy` succeeded, so a release whose deploy was skipped does not count. seed_trial.py, reset_trial.py and deploy_trial.py said a trial is checked before production; a push now deploys production first, and their help text says the trial comes after, as operations.md does. Rejected: asking production's /api/build, the better receipt. The wizard holds only the trial's Access token at that stage and has no production URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the owner's 2026-11-03 decisions: a push to main releases and deploys, a dispatch may opt out of the deploy, the hourly cron is not avoided, and only the head of its ref deploys automatically. It keeps the surviving half of the old separation (build and deploy fail as separate jobs) and retires the other (a person picks the hour). Rejected: keeping both workflows dispatch-only, holding deploys away from the cron hour, workflow_run, and an ancestry guard. Consequences stated plainly: any push to main, which has no branch protection, deploys production; a digest pin lasts until the next push; a queued dispatch replaces a waiting push run; re-runs of old runs skip their deploy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
architecture.md §6, operations.md, trial-deployment.md, the accepted release-image-built-in-ci spec, CLAUDE.md and the CHANGELOG now say the release runs on push and dispatch and deploys the released head unless a dispatch opts out, that the cron is not avoided, and that a digest pin lasts until the next push. operations.md also states what a queued run replaces and how to recover, and that a failed deploy is retried by re-running all jobs. "Staging first, production after" became "The trial deployment, after production". §7 waits for the landed hash. Rejected: rewriting the 2026 CHANGELOG entry that calls both workflows dispatch-only; it records what shipped then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Review of #153: I found no correctness bugs, data loss or security problems in the workflow changes. I only read the diff and What I checked:
Risks the ADR already admits, which you may want to weigh before merging:
Nothing here blocks the merge. |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Why
Every production deploy needed two dispatches, and the second one (
deploy.yml) had to be run by the owner, because Claude Code's auto mode refuses an agent's production deploy. The owner decided on 2026-11-03 that a release should deploy on its own (ADR 0024).What
release-image.ymlruns on every push tomainand on dispatch. A dispatch has a booleandeployinput, default true.releasesucceeds, acurrentjob checks that the released commit is still the head of the ref the run was for (git ls-remote). Then adeployjob callsdeploy.ymlthroughworkflow_callon the same commit, so it deploys the digest recorded in that commit'simage/<sha7>tag.(push || inputs.deploy) && current == 'true' && attempt == run_attempt.inputs.deployevaluates to an empty string, which compares equal to false. Soinputs.deploy != falsewould have skipped every push; the tests pin this.deploy.ymlis still dispatchable withimage_tagfor rollback by digest, and that path does not go through the guard. Its concurrency moved from workflow level to its job, with the same group and no cancelling.scripts/trial-wizard.shreports production's last deploy from either path. Before, it read onlydeploy.ymlruns, and a called workflow has no run of its own.release-image-built-in-cispec, CLAUDE.md (the owner approved the line) and the CHANGELOG. §7 follows on main after the merge.Owner decisions (2026-11-03)
deploy.main.Consequences to know
maindeploys production, andmainhas no branch protection;scripts/check.shin the release job is the only gate.main.deploy.yml. Re-running only the faileddeployjob skips it.Verification
scripts/check.sh: 4851 passed on the branch tip. Each intermediate commit passes the workflow, trial and doc tests.deploy-appgroup serialises with a manual deploy, and the called job's name (deploy / deploy, seen on another project's run) that the wizard matches.🤖 Generated with Claude Code