bug(deploy): a bot-published release does not fire deploy.yml — every automated cut publishes without deploying #40

Closed
opened 2026-08-06 11:54:52 +02:00 by bosun · 1 comment
Owner

What happened

The v0.2.0 cut succeeded end to end and the deploy never ran. purser is still serving the previous image.

main 10363351 · tags v0.2.0 + v0.1.0
release v0.2.0 published 2026-08-06T11:50:19+02:00 · draft=false
release / decide + act / go-ci   all success
deploy runs ever                 0
running container   936723cf6178 · up 13h · healthy · 200 via ingress
src tree            1af005b · no tag at HEAD

deploy.yml is on: release: types: [published]. A non-draft release was published. The workflow did not run.

The zero is real, not a filter artifact. deploy.yml's job is named deploy purser to /srv/docker/purser, so a deploy-substring filter would match it; it appears in none of the distinct job names across 237 runs returned by the same query (non-zero result ⇒ the query works).

Probable mechanism — labelled probable, NOT measured

v0.2.0   author=Ghost   deploy.yml PRESENT at tag   → no deploy
v0.1.0   author=alex    deploy.yml ABSENT at tag    → no deploy

v0.1.0 is fully explained by absence (measured previously by @quartermaster, and it must not be generalised — see #36). v0.2.0 is not explained that way: the workflow is present and the trigger event occurred.

The one variable that changed is who published the release. release.yml uses secrets: inherit with no dedicated release token, so the release is created by the Actions token and appears as Ghost. That is consistent with Forgejo suppressing workflow events generated by its own Actions token — the same anti-recursion safeguard recorded in /srv/CLAUDE.md for bot-token pushes producing zero commit statuses.

⚠️ Not confirmed. The discriminator is a human-published release firing the deploy, which cannot be run cheaply without another cut. Do not fix on this hypothesis before testing it — the same "plausible mechanism, never verified" shape cost most of 2026-08-06 on this repo.

Why it matters

On the automated path, every cut publishes and never deploys. #19's pipeline is inert exactly where it was built to run, and it fails silently — green release job, published release, no red anywhere. Absence of a signal is not evidence of health.

It also means v0.2.0 is released but not deployed, and will stay that way; the plan is to supersede it rather than back-fill.

Scope

  1. Confirm the mechanism before changing anything. Establish whether the Actions-token identity is what suppresses the release: published event here.
  2. Fix so an automated cut reaches the deploy. Candidate directions, not a decision:
    • create the release under a real token (a dedicated release PAT) so the event is not self-generated — note this also touches the path-gamma manifest question in release-toolkit#661, same root cause
    • or invoke the deploy from release.yml directly rather than via a release event
    • or a tag-push trigger — but verify it is not suppressed by the same safeguard, since the tag is also bot-pushed
  3. Make the silence loud: a cut that publishes without deploying should leave something red or otherwise visible. Today nothing distinguishes "deployed" from "never ran".

Acceptance criteria

  • Superseded — these four moved to #39 when this tracker was closed as its duplicate.

They are struck rather than ticked because none of them was true at close time, and ticking a
state-assertion that the substrate does not back is the failure /srv/CLAUDE.md § Acceptance-criteria
tick discipline
exists to prevent. The ticked line above is an action AC — the supersession is what
actually happened, and it is verifiable.

- [ ] Mechanism confirmed by measurement, not inference; the confirming observation recorded here
- [ ] An automated release cut results in deploy.yml running
- [ ] Verified on a real cut (v0.3.0), not only in dry-run — the deployed binary reports the new version
- [ ] A cut that publishes without deploying is detectable without someone thinking to look

Live status of each, on #39:

mechanism confirmed          DONE — no RELEASE_TOOLKIT_TOKEN; Actions-token events do not cascade
automated cut → deploy runs  OPEN — the v0.3.0 draft is cut and awaiting the operator's Publish
verified on a real cut       OPEN — same gate; the deployed binary must report v0.3.0
silence made detectable      OPEN — nothing yet distinguishes "deployed" from "never ran"

⚠️ I closed this tracker and left four unticked state-assertions behind, which is exactly the
closed-with-unticked drift scripts/ac-state-audit.py --closed-unticked scans for.
Filing the
duplicate-close was correct; leaving the ACs in a state that reads as abandoned work was not.

  • #19 — the deploy pipeline this makes inert
  • #36 — release/deploy facts measured during the stuck cut; the v0.1.0 deploy.yml-absent precedent and why it must not be generalised
  • #32 — rollback on failed probe; landing before the first real deploy
  • release-toolkit#661 — path-gamma manifest never delivered; plausibly the same missing-release-token root
  • /srv/CLAUDE.md § reflex table, A GATE'S SILENCE — passed and never-ran emit the same absence of red

Anchor

Measured by Bosun 2026-08-06 immediately after the operator's v0.2.0 cut. Service was never at risk: nothing was replaced, no probe fired, no rollback needed.

## What happened The `v0.2.0` cut succeeded end to end and **the deploy never ran**. purser is still serving the previous image. ``` main 10363351 · tags v0.2.0 + v0.1.0 release v0.2.0 published 2026-08-06T11:50:19+02:00 · draft=false release / decide + act / go-ci all success deploy runs ever 0 running container 936723cf6178 · up 13h · healthy · 200 via ingress src tree 1af005b · no tag at HEAD ``` `deploy.yml` is `on: release: types: [published]`. A non-draft release **was** published. The workflow did not run. **The zero is real, not a filter artifact.** `deploy.yml`'s job is named `deploy purser to /srv/docker/purser`, so a `deploy`-substring filter would match it; it appears in **none** of the distinct job names across **237** runs returned by the same query (non-zero result ⇒ the query works). ## Probable mechanism — labelled probable, NOT measured ``` v0.2.0 author=Ghost deploy.yml PRESENT at tag → no deploy v0.1.0 author=alex deploy.yml ABSENT at tag → no deploy ``` `v0.1.0` is fully explained by absence (measured previously by @quartermaster, and it must not be generalised — see #36). `v0.2.0` is **not** explained that way: the workflow is present and the trigger event occurred. The one variable that changed is **who published the release**. `release.yml` uses `secrets: inherit` with no dedicated release token, so the release is created by the Actions token and appears as `Ghost`. That is consistent with Forgejo suppressing workflow events generated by its own Actions token — the same anti-recursion safeguard recorded in `/srv/CLAUDE.md` for bot-token pushes producing zero commit statuses. ⚠️ **Not confirmed.** The discriminator is a human-published release firing the deploy, which cannot be run cheaply without another cut. **Do not fix on this hypothesis before testing it** — the same "plausible mechanism, never verified" shape cost most of 2026-08-06 on this repo. ## Why it matters **On the automated path, every cut publishes and never deploys.** `#19`'s pipeline is inert exactly where it was built to run, and it fails *silently* — green release job, published release, no red anywhere. Absence of a signal is not evidence of health. It also means `v0.2.0` is **released but not deployed**, and will stay that way; the plan is to supersede it rather than back-fill. ## Scope 1. **Confirm the mechanism before changing anything.** Establish whether the Actions-token identity is what suppresses the `release: published` event here. 2. Fix so an automated cut reaches the deploy. Candidate directions, not a decision: - create the release under a real token (a dedicated release PAT) so the event is not self-generated — note this also touches the path-gamma manifest question in release-toolkit#661, same root cause - or invoke the deploy from `release.yml` directly rather than via a `release` event - or a tag-push trigger — **but verify it is not suppressed by the same safeguard**, since the tag is also bot-pushed 3. Make the silence loud: a cut that publishes without deploying should leave something red or otherwise visible. Today nothing distinguishes "deployed" from "never ran". ## Acceptance criteria - [x] **Superseded — these four moved to #39 when this tracker was closed as its duplicate.** They are struck rather than ticked because **none of them was true at close time**, and ticking a state-assertion that the substrate does not back is the failure `/srv/CLAUDE.md` § *Acceptance-criteria tick discipline* exists to prevent. The ticked line above is an **action** AC — the supersession is what actually happened, and it is verifiable. ~~- [ ] Mechanism confirmed by measurement, not inference; the confirming observation recorded here~~ ~~- [ ] An automated release cut results in `deploy.yml` running~~ ~~- [ ] Verified on a real cut (`v0.3.0`), not only in dry-run — the deployed binary reports the new version~~ ~~- [ ] A cut that publishes without deploying is detectable without someone thinking to look~~ **Live status of each, on #39:** ``` mechanism confirmed DONE — no RELEASE_TOOLKIT_TOKEN; Actions-token events do not cascade automated cut → deploy runs OPEN — the v0.3.0 draft is cut and awaiting the operator's Publish verified on a real cut OPEN — same gate; the deployed binary must report v0.3.0 silence made detectable OPEN — nothing yet distinguishes "deployed" from "never ran" ``` ⚠️ **I closed this tracker and left four unticked state-assertions behind, which is exactly the closed-with-unticked drift `scripts/ac-state-audit.py --closed-unticked` scans for.** Filing the duplicate-close was correct; leaving the ACs in a state that reads as abandoned work was not. ## Related - `#19` — the deploy pipeline this makes inert - `#36` — release/deploy facts measured during the stuck cut; the `v0.1.0` deploy.yml-absent precedent and why it must not be generalised - `#32` — rollback on failed probe; landing before the first real deploy - release-toolkit#661 — path-gamma manifest never delivered; plausibly the same missing-release-token root - `/srv/CLAUDE.md` § reflex table, *A GATE'S SILENCE* — passed and never-ran emit the same absence of red ## Anchor Measured by Bosun 2026-08-06 immediately after the operator's `v0.2.0` cut. Service was never at risk: nothing was replaced, no probe fired, no rollback needed.
bosun closed this issue 2026-08-06 11:56:40 +02:00
Author
Owner

Closing as duplicate of #39, which @engineer filed a minute earlier and which traces the mechanism further — down to purser having no RELEASE_TOOLKIT_TOKEN, so the release is created by the Actions token. Measurements, eliminated alternatives, the discriminator and the ACs from this tracker are folded into #39. Nothing here is lost; do not work from this one.

Closing as duplicate of #39, which @engineer filed a minute earlier and which traces the mechanism further — down to purser having no `RELEASE_TOOLKIT_TOKEN`, so the release is created by the Actions token. Measurements, eliminated alternatives, the discriminator and the ACs from this tracker are folded into #39. Nothing here is lost; do not work from this one.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/purser#40
No description provided.