You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When canary-rollout promotes an agent into a ring, it sometimes moves only the bare channel tag <agent>/<tier> and never creates the major-scoped<agent>/v<M>-<tier>. The canary itself keeps working — _resolved_channel prefers the v-scoped tag and falls back to the bare one — so the gap is invisible until a stub deploy needs the v-scoped ref, at which point it pins a nonexistent tag and the consumer fails at startup.
Evidence
Sweep of every bare channel tag on petry-projects/.github against its v-scoped counterpart:
Agent
Has
Missing
apply-repo-settings
ring1
v1-ring1 — created by hand 2026-09-02 to unblock #1045
persona-mention
ring0, ring1
v1-ring0, v1-ring1 — still missing
Every other agent has both forms at every tier it has reached (add-to-project/v1-ring1, agent-shield/v2-ring1, auto-rebase/v2-ring1, dependency-audit/v2-ring1, pr-auto-review/v1-ring1, …).
persona-mention has v1-next and releases v1.0.0–v1.0.3, so a major exists to scope against — this is not a "no major yet" case.
For apply-repo-settings the inconsistency is sharp and one hop wide:
v1-next edbac2551e4c
v1-ring0 edbac2551e4c ← next→ring0 created the v-scoped form
ring1 edbac2551e4c ← ring0→ring1 did NOT
Same agent, same candidate, consecutive promotions.
Why it stays hidden
_resolved_channel (epic #657 Phase F4) prefers <agent>/v<M>-<tier> and falls back to the bare tier when it is absent, so evaluation, promotion and rollback all keep functioning on a bare-only tag.
The consumer side does not fall back. deploy-standard-workflows.sh keys the stub pin on the channel major — the highest existing <base>/v<M>-<tier> tag — and its own comment states the rule:
Once a channel tag exists, a bare stub is drift and must migrate to the tier's v<M>-<tier> form.
So for any agent that has any v-scoped channel tag, the deploy emits v<M>-<tier> for the target repo's tier. If the promotion never created that ref, every stub deployed to that tier pins a tag that does not exist — a fleet-wide startup_failure for that workflow.
This is what blocked #1045: TalkTerm and bmad-bgreat-suite resolve to tier=ring1, so the stub pins @apply-repo-settings/v1-ring1, which the ring0→ring1 promotion had not created.
AC1 — promotion must move both forms, or neither
A ring promotion that moves <agent>/<tier> must also create/move <agent>/v<M>-<tier> whenever the agent has a channel major. Treat the pair as one atomic operation so the two can never diverge.
AC2 — backfill persona-mention
Create persona-mention/v1-ring0 and persona-mention/v1-ring1 at the commits their bare tags point to. Until then, any stub deploy targeting a ring0/ring1 repo for persona-mention will pin a nonexistent ref.
AC3 — a drift check
canary-rollout drift (the read-only registry audit) should report any bare channel tag lacking its v-scoped counterpart for an agent that has a channel major. This sweep found the problem in one pass and should not have needed a human to run it:
gh api repos/petry-projects/.github/git/refs/tags --jq '.[].ref|sub("refs/tags/";"")'> tags.txt
fortierin next ring0 ring1 stable;doforbarein$(grep -E "/${tier}$" tags.txt);do
agent="${bare%/*}"
grep -qE "^${agent}/v[0-9]+-${tier}$" tags.txt ||echo"$agent — has /$tier but NO v<M>-$tier"donedone
AC4 — regression coverage
A test asserting that promoting an agent which has a channel major produces both the bare and the v-scoped tag at the target tier.
Note on the manual tag.apply-repo-settings/v1-ring1 was created by hand at edbac2551e4c091d5ee0c86195f9a34682c2bfe1 (matching ring1) to unblock the #1045 rollout. It is a lightweight ref, consistent with how ring1 and v1-ring0 are stored. It should be adopted by the automation once AC1 lands, not treated as a special case.
Filed from a manual compliance sweep, 2026-09-02.
CORRECTION 2026-09-07 — AC1 above is wrong. Replaced by AC1′.
AC1 as originally written ("promotion must move both forms, or neither") misreads the design. The promotion is meant to move exactly one tag. From canary-rollout.sh:
# Move the RESOLVED frontier tag (major-scoped-channels epic #657, F4): advance the# v-scoped `<agent>/v<M>-<tier>` within its major line when it exists, else the legacy# bare `<agent>/<tier>`. A v2 promotion never touches a v1 tag.local frontier_tag; frontier_tag="$(_resolved_channel_tag "$agent""$frontier")"
and _resolved_channel prefers the v-scoped tag only when it already resolves to a real oid, else falls back to the bare tier. Moving both would break the F4 contract — a bare-only legacy agent must not sprout v-scoped tags, and a v2 line must never touch v1.
AC1′ — the real defect is bootstrap, not duplication
Once an agent has a channel major (any <agent>/v<M>-<tier> tag exists anywhere), the v-scoped line can never extend to a tier it has not already reached:
Promotion to tier T calls _resolved_channel "$agent" "$T".
<agent>/v<M>-T does not exist yet → its commit is empty → _looks_like_oid fails.
Resolution falls back to the bare <agent>/T and moves that.
<agent>/v<M>-T is therefore never created, and step 2 fails identically on every future promotion.
The v-scoped line is stranded at whatever tier it was first seeded at. This is exactly what happened to apply-repo-settings:
v1-next edbac2551e4c ← seeded
v1-ring0 edbac2551e4c ← seeded
ring1 edbac2551e4c ← ring0→ring1 fell back to the bare tag
(v1-ring1 never created; added by hand 2026-09-02)
Required behaviour: when _agent_current_major returns a major for the agent, promotion to tier T must create<agent>/v<M>-T at the candidate rather than falling back to the bare tier. The fallback stays correct for agents with no channel major at all (the legacy bare-only fleet), so the condition is "has a channel major", not "the tag already exists".
Note _gh_move_tag already creates a missing ref (PATCH, then POST on a genuine 404/422), so the primitive supports this — only the resolution step needs to stop falling back when a major exists.
AC2 — unchanged, and now more urgent
Backfill persona-mention/v1-ring0 and persona-mention/v1-ring1 at the commits their bare tags point to. persona-mention has v1-next and releases v1.0.0–v1.0.3, so it has a channel major and is squarely in the AC1′ trap.
Urgency is raised by #1088: once RING_REUSABLES is derived from the registry, persona-mention becomes ring-aware in deploy-standard-workflows.sh, and emit_ref_for will compute @persona-mention/v1-ring0 / v1-ring1 for repos at those tiers — refs that do not exist. #1088's own AC2 (refuse to deploy an unresolvable ref) turns that into a loud failure rather than a broken fleet, but the tags still need to exist for the deploy to succeed.
Current gaps, verified 2026-09-07 — apply-repo-settings no longer appears because its v1-ring1 was created by hand:
persona-mention — has /ring0, no v<M>-ring0
persona-mention — has /ring1, no v<M>-ring1
AC4′ — coverage, restated for AC1′
Replace the original AC4. Assert that promoting an agent which has a channel major into a tier whose v-scoped tag does not yet exist creates<agent>/v<M>-<tier> and does not move the bare <agent>/<tier>. Pair it with the inverse: an agent with no channel major still moves the bare tier tag and creates no v-scoped tag.
AC3 — unchanged
The drift sweep remains as written and would have caught both agents in one pass.
SCOPE FENCE 2026-09-08 — released to dev-lead for the CODE fix only
In scope for this issue
AC1′, AC3, AC4′ — the resolution/bootstrap fix in scripts/canary-rollout.sh, the drift check, and coverage.
OUT of scope — do not attempt
AC2 (backfill persona-mention/v1-ring0 and persona-mention/v1-ring1) is a maintainer action. It requires creating release channel tags, which an agent must not do. Leave it entirely alone; it is tracked here for the maintainer and will be handled separately.
More generally, for this issue: do not create, move, delete or cut any tag or release. The fix is to the code that decides which tag to move — not to the tags themselves. A correct implementation changes only scripts/canary-rollout.sh (and/or scripts/lib/ring-pins.sh) plus tests.
Verification without touching tags
Prove AC1′ with the bats suite, not by running a promotion. A promotion against the live host would move real channel tags and reposition agents in the canary. tests/canary_rollout.bats already stubs gh; extend that pattern.
Every agent that later gains a channel major hits the same bootstrap trap the first time it is promoted into a tier its v-scoped line has not reached. apply-repo-settings hit it at ring0→ring1 and needed a hand-cut tag; persona-mention is stuck at two tiers. Without AC1′ this recurs silently for the next agent, and it is only ever noticed when a stub deploy fails.
Maintainer review notes on PR #1097 (added to the BODY, 2026-09-10 — dev-lead cannot read issue comments, #1566)
Both verified against the PR head 7ce32b16, not inferred. Neither blocks the approach — the fix
is right and _resolved_channel's removal of the bare-tier fallback for established-major agents
does make every unreached v-scoped tier promotable, including ring1→stable. These are two defects
to clear before merge.
1. channel_commit's docstring now contradicts the function (scripts/canary-rollout.sh:341-346)
The comment still reads:
Resolution is fall-back-safe on the major dimension (epic #657 F4): prefer the v-scoped tag, else the bare tier.
That is precisely the behaviour this PR removes. _resolved_channel (line 323) now returns the
v-scoped tag with an empty commit when the tag is absent, and only falls back to the bare tier for
an agent with no channel major at all. The next reader debugging a resolution will be misled by
a comment sitting directly above the call that contradicts it. Update it to describe the
established-major / legacy-bare-only split.
2. AC3's drift report cannot see the agent AC3 exists to catch (scripts/canary-rollout.sh:2530)
[ -n"$(_channel_tag_commit "$agent""$tier")" ] ||continue# no bare tier tag → nothing to pair
A tier with no bare tag is skipped, so a gap is only visible when the bare side exists. Live
state today:
agent
bare tags
v-scoped tags
missing
seen by drift?
apply-repo-settings
next, ring0, ring1, stable
v1-next, v1-ring0, v1-ring1
v1-stable
yes
persona-mention
ring0, ring1
v1-next, v1-ring0, v1-ring1
v1-stable, and bare next/stable
no
persona-mention has no bare next and no bare stable, so its missing v1-stable is invisible
to the report — and persona-mention is the one agent this issue's own Evidence table names as "still missing". The AC3 report will read clean for it while the promoter (correctly) creates the
tag underneath.
A tier can drift by missing either side of the pair. Suggested AC refinement: for an agent with
an established channel major, report a tier where either<agent>/<tier> or <agent>/v<M>-<tier> is absent while the other is present — or, more simply, report any tier the
agent has reached whose v-scoped form does not exist, independent of the bare form.
Not a defect, recorded so it is not re-litigated
_gh_move_tag's 422-vs-404 misclassification is fixed on main — the matcher accepts "reference does not exist" alongside not found / http 404. It is not a contributing cause here.
Status 2026-09-13: core fix merged in PR #1097; the remaining scope is exactly two items
PR #1097 merged at 782e005f (2026-09-13T04:03Z). It delivered bare-tier fallback removal for established-major agents, _agent_current_channel_major with all three call sites switched, the release-major docstring on _agent_current_major, and the divergent release/channel test. Do not redo any of that. The PR carried no closing reference, which is why this issue is still open.
Remaining; implement only this. Both are verified present on main at 782e005f:
AC3 drift blind spot, scripts/canary-rollout.sh:2555._channel_tag_major_gaps skips any tier with no bare tag (# no bare tier tag → nothing to pair), so persona-mention, which has no bare next or stable, reports clean while v1-stable is missing. Report a tier the agent has reached whose v<M>-<tier> form is absent regardless of whether the bare form exists. Test: an agent with v1-next, v1-ring0, ring0, no bare stable, no v1-stable, placed in a ring layout where stable is reached, is flagged.
Summary
When
canary-rolloutpromotes an agent into a ring, it sometimes moves only the bare channel tag<agent>/<tier>and never creates the major-scoped<agent>/v<M>-<tier>. The canary itself keeps working —_resolved_channelprefers the v-scoped tag and falls back to the bare one — so the gap is invisible until a stub deploy needs the v-scoped ref, at which point it pins a nonexistent tag and the consumer fails at startup.Evidence
Sweep of every bare channel tag on
petry-projects/.githubagainst its v-scoped counterpart:apply-repo-settingsring1v1-ring1— created by hand 2026-09-02 to unblock #1045persona-mentionring0,ring1v1-ring0,v1-ring1— still missingEvery other agent has both forms at every tier it has reached (
add-to-project/v1-ring1,agent-shield/v2-ring1,auto-rebase/v2-ring1,dependency-audit/v2-ring1,pr-auto-review/v1-ring1, …).persona-mentionhasv1-nextand releasesv1.0.0–v1.0.3, so a major exists to scope against — this is not a "no major yet" case.For
apply-repo-settingsthe inconsistency is sharp and one hop wide:Same agent, same candidate, consecutive promotions.
Why it stays hidden
_resolved_channel(epic #657 Phase F4) prefers<agent>/v<M>-<tier>and falls back to the bare tier when it is absent, so evaluation, promotion and rollback all keep functioning on a bare-only tag.The consumer side does not fall back.
deploy-standard-workflows.shkeys the stub pin on the channel major — the highest existing<base>/v<M>-<tier>tag — and its own comment states the rule:So for any agent that has any v-scoped channel tag, the deploy emits
v<M>-<tier>for the target repo's tier. If the promotion never created that ref, every stub deployed to that tier pins a tag that does not exist — a fleet-widestartup_failurefor that workflow.This is what blocked #1045:
TalkTermandbmad-bgreat-suiteresolve totier=ring1, so the stub pins@apply-repo-settings/v1-ring1, which thering0→ring1promotion had not created.AC1 — promotion must move both forms, or neither
A ring promotion that moves
<agent>/<tier>must also create/move<agent>/v<M>-<tier>whenever the agent has a channel major. Treat the pair as one atomic operation so the two can never diverge.AC2 — backfill
persona-mentionCreate
persona-mention/v1-ring0andpersona-mention/v1-ring1at the commits their bare tags point to. Until then, any stub deploy targeting a ring0/ring1 repo forpersona-mentionwill pin a nonexistent ref.AC3 — a drift check
canary-rollout drift(the read-only registry audit) should report any bare channel tag lacking its v-scoped counterpart for an agent that has a channel major. This sweep found the problem in one pass and should not have needed a human to run it:AC4 — regression coverage
A test asserting that promoting an agent which has a channel major produces both the bare and the v-scoped tag at the target tier.
Note on the manual tag.
apply-repo-settings/v1-ring1was created by hand atedbac2551e4c091d5ee0c86195f9a34682c2bfe1(matchingring1) to unblock the #1045 rollout. It is a lightweight ref, consistent with howring1andv1-ring0are stored. It should be adopted by the automation once AC1 lands, not treated as a special case.Filed from a manual compliance sweep, 2026-09-02.
CORRECTION 2026-09-07 — AC1 above is wrong. Replaced by AC1′.
AC1 as originally written ("promotion must move both forms, or neither") misreads the design. The promotion is meant to move exactly one tag. From
canary-rollout.sh:and
_resolved_channelprefers the v-scoped tag only when it already resolves to a real oid, else falls back to the bare tier. Moving both would break the F4 contract — a bare-only legacy agent must not sprout v-scoped tags, and a v2 line must never touch v1.AC1′ — the real defect is bootstrap, not duplication
Once an agent has a channel major (any
<agent>/v<M>-<tier>tag exists anywhere), the v-scoped line can never extend to a tier it has not already reached:_resolved_channel "$agent" "$T".<agent>/v<M>-Tdoes not exist yet → its commit is empty →_looks_like_oidfails.<agent>/Tand moves that.<agent>/v<M>-Tis therefore never created, and step 2 fails identically on every future promotion.The v-scoped line is stranded at whatever tier it was first seeded at. This is exactly what happened to
apply-repo-settings:Required behaviour: when
_agent_current_majorreturns a major for the agent, promotion to tier T must create<agent>/v<M>-Tat the candidate rather than falling back to the bare tier. The fallback stays correct for agents with no channel major at all (the legacy bare-only fleet), so the condition is "has a channel major", not "the tag already exists".Note
_gh_move_tagalready creates a missing ref (PATCH, then POST on a genuine 404/422), so the primitive supports this — only the resolution step needs to stop falling back when a major exists.AC2 — unchanged, and now more urgent
Backfill
persona-mention/v1-ring0andpersona-mention/v1-ring1at the commits their bare tags point to.persona-mentionhasv1-nextand releasesv1.0.0–v1.0.3, so it has a channel major and is squarely in the AC1′ trap.Urgency is raised by #1088: once
RING_REUSABLESis derived from the registry,persona-mentionbecomes ring-aware indeploy-standard-workflows.sh, andemit_ref_forwill compute@persona-mention/v1-ring0/v1-ring1for repos at those tiers — refs that do not exist. #1088's own AC2 (refuse to deploy an unresolvable ref) turns that into a loud failure rather than a broken fleet, but the tags still need to exist for the deploy to succeed.Current gaps, verified 2026-09-07 —
apply-repo-settingsno longer appears because itsv1-ring1was created by hand:AC4′ — coverage, restated for AC1′
Replace the original AC4. Assert that promoting an agent which has a channel major into a tier whose v-scoped tag does not yet exist creates
<agent>/v<M>-<tier>and does not move the bare<agent>/<tier>. Pair it with the inverse: an agent with no channel major still moves the bare tier tag and creates no v-scoped tag.AC3 — unchanged
The drift sweep remains as written and would have caught both agents in one pass.
SCOPE FENCE 2026-09-08 — released to dev-lead for the CODE fix only
In scope for this issue
AC1′, AC3, AC4′ — the resolution/bootstrap fix in
scripts/canary-rollout.sh, the drift check, and coverage.OUT of scope — do not attempt
AC2 (backfill
persona-mention/v1-ring0andpersona-mention/v1-ring1) is a maintainer action. It requires creating release channel tags, which an agent must not do. Leave it entirely alone; it is tracked here for the maintainer and will be handled separately.More generally, for this issue: do not create, move, delete or cut any tag or release. The fix is to the code that decides which tag to move — not to the tags themselves. A correct implementation changes only
scripts/canary-rollout.sh(and/orscripts/lib/ring-pins.sh) plus tests.Verification without touching tags
Prove AC1′ with the bats suite, not by running a promotion. A promotion against the live host would move real channel tags and reposition agents in the canary.
tests/canary_rollout.batsalready stubsgh; extend that pattern.Related work in flight
RING_REUSABLESnow equals the registry, and the deploy refuses a non-resolving pin. That refusal is what currently turns thepersona-mentiongap into a loud error rather than a broken fleet.ring_tier_for_repoderived from the registry. Same file family; rebase on currentmainand coordinate if both are in flight.Why AC1′ matters beyond persona-mention
Every agent that later gains a channel major hits the same bootstrap trap the first time it is promoted into a tier its v-scoped line has not reached.
apply-repo-settingshit it atring0→ring1and needed a hand-cut tag;persona-mentionis stuck at two tiers. Without AC1′ this recurs silently for the next agent, and it is only ever noticed when a stub deploy fails.Maintainer review notes on PR #1097 (added to the BODY, 2026-09-10 — dev-lead cannot read issue comments, #1566)
Both verified against the PR head
7ce32b16, not inferred. Neither blocks the approach — the fixis right and
_resolved_channel's removal of the bare-tier fallback for established-major agentsdoes make every unreached v-scoped tier promotable, including
ring1→stable. These are two defectsto clear before merge.
1.
channel_commit's docstring now contradicts the function (scripts/canary-rollout.sh:341-346)The comment still reads:
That is precisely the behaviour this PR removes.
_resolved_channel(line 323) now returns thev-scoped tag with an empty commit when the tag is absent, and only falls back to the bare tier for
an agent with no channel major at all. The next reader debugging a resolution will be misled by
a comment sitting directly above the call that contradicts it. Update it to describe the
established-major / legacy-bare-only split.
2. AC3's drift report cannot see the agent AC3 exists to catch (
scripts/canary-rollout.sh:2530)A tier with no bare tag is skipped, so a gap is only visible when the bare side exists. Live
state today:
apply-repo-settingsnext,ring0,ring1,stablev1-next,v1-ring0,v1-ring1v1-stablepersona-mentionring0,ring1v1-next,v1-ring0,v1-ring1v1-stable, and barenext/stablepersona-mentionhas no barenextand no barestable, so its missingv1-stableis invisibleto the report — and
persona-mentionis the one agent this issue's own Evidence table names as"still missing". The AC3 report will read clean for it while the promoter (correctly) creates the
tag underneath.
A tier can drift by missing either side of the pair. Suggested AC refinement: for an agent with
an established channel major, report a tier where either
<agent>/<tier>or<agent>/v<M>-<tier>is absent while the other is present — or, more simply, report any tier theagent has reached whose v-scoped form does not exist, independent of the bare form.
Not a defect, recorded so it is not re-litigated
_gh_move_tag's 422-vs-404 misclassification is fixed onmain— the matcher accepts"reference does not exist"alongsidenot found/http 404. It is not a contributing cause here.Status 2026-09-13: core fix merged in PR #1097; the remaining scope is exactly two items
PR #1097 merged at
782e005f(2026-09-13T04:03Z). It delivered bare-tier fallback removal for established-major agents,_agent_current_channel_majorwith all three call sites switched, the release-major docstring on_agent_current_major, and the divergent release/channel test. Do not redo any of that. The PR carried no closing reference, which is why this issue is still open.Remaining; implement only this. Both are verified present on
mainat782e005f:scripts/canary-rollout.sh:364-365.channel_commitstill says "Resolution is fall-back-safe on the major dimension (epic Story (F): major-version boundary in the channel/ring model [#604] #657 F4): prefer the v-scoped tag, else the bare tier." Since feat: implement issue #1065 — canary-rollout ring promotion can skip the v-scoped channel tag, breaking every stub deployed to that tier #1097, that is false for any agent with a channel major. Rewrite it to describe the established-major vs legacy-bare-only split.scripts/canary-rollout.sh:2555._channel_tag_major_gapsskips any tier with no bare tag (# no bare tier tag → nothing to pair), sopersona-mention, which has no barenextorstable, reports clean whilev1-stableis missing. Report a tier the agent has reached whosev<M>-<tier>form is absent regardless of whether the bare form exists. Test: an agent withv1-next,v1-ring0,ring0, no barestable, nov1-stable, placed in a ring layout where stable is reached, is flagged.Close this issue when those two land.