canary-rollout PROMOTE fails for cross-repo (#482) agents: uses local 'git tag -f', not host gh-api (companion to #1049) #1054
Description
🔴 P1 — cross-repo agents can be evaluated but NOT promoted. #1049 made tag reading host-aware, but the promote write path still moves tags with local git. A gated manual promote of a ready reusable:
`gh workflow run canary-rollout.yml -f command=promote -f agent=dependabot-automerge -f dry_run=false` → gate said PROMOTE, then:
advancing dependabot-automerge/stable -> b7e6b67063f4
fatal: cannot update ref 'refs/tags/dependabot-automerge/stable': trying to write ref
'refs/tags/dependabot-automerge/stable' with nonexistent object b7e6b67063f4b5673ca6955b2630b315adc1ccd3
##[error]exit code 128
Root cause
cmd_promote does git tag -f "$agent/$frontier" "$cand" && git push --force origin "$agent/$frontier" — local-checkout tags pushed to THIS repo's origin (.github-private). For a cross-repo agent (host: petry-projects/.github), the candidate commit b7e6b670 isn't in the local object DB (it's on .github), and the channel tag lives on .github, not here → git update-ref fails, and even if it didn't, it'd write to the wrong repo.
Fix
For host ≠ this repo, perform the promote against host via the existing primitive — scripts/cut-release.sh <agent> <version> --channel <target> --promote --push already does the host-aware gh-api channel move (#959/#992) using the App token. Delegate to it (or replicate its gh_move_tag against host). Keep the local git tag -f path only for this-repo agents (dev-lead, pr-review). Same split #1049 added for reads. Add a bats/integration case: promoting a cross-repo agent must move the tag on .github, not fail on a nonexistent local object.
Impact: none of the 6 #482 reusables can be promoted until this lands — the 2 currently gate-PROMOTE (dependabot-automerge, dependabot-rebase) are stuck. Companion to #1049 (reads) — introduced with the promote logic in #1002/#1026; --promote primitive from #959/#992.
Activity
don-petry commentedon Jul 3, 2026
Dev-Lead Implementation Plan
Issue: #1054 — canary-rollout PROMOTE fails for cross-repo (#482) agents: uses local 'git tag -f', not host gh-api (companion to #1049)
Scope
Make
cmd_promoteinscripts/canary-rollout.shhost-aware: for a cross-repo agent (host≠ this repo) move the<agent>/<frontier>channel tag onhostvia the GitHub API (mirroring cut-release.sh'sgh_move_tag/ the #1049 read-path split), keeping the localgit tag -f+git pushpath only for this-repo agents (dev-lead, pr-review).Implementation Checklist
_gh_move_tag <repo> <tag> <sha>helper toscripts/canary-rollout.sh(force-move if exists, create if not — mirrors cut-release.shgh_move_tag)cmd_promote: cross-repo →_gh_move_tag "$host" "$agent/$frontier" "$cand"; this-repo → existinggit tag -f+git push --force origin--dry-runoutput host-aware for the cross-repo caseTests to Write
tests/canary_rollout.bats: promoting a cross-repo agent moves the channel tag onhostvia gh api and exits 0 (regression: the old localgit tag -fpath fails on the nonexistent local object)tests/canary_rollout.bats: cross-repopromote --dry-runprints the host-aware move and touches neither git nor the APITest Command
bats tests/canary_rollout.bats(full:git ls-files 'tests/*.bats' | xargs bats)don-petry commentedon Jul 3, 2026
Dev-Lead: Implementation Complete
Plan Execution
_gh_move_tag <repo> <tag> <sha>helper toscripts/canary-rollout.sh(force-move if the ref exists viaPATCH .../git/refs/tags/<tag>, else create viaPOST .../git/refs) — mirrorscut-release.sh'sgh_move_tag(feat: wire cut-release.sh cross-repo publish via gh api (#872) #959/feat(cut-release): add --promote for ring-to-ring channel moves (#501) #992)cmd_promote: cross-repo agent (host≠THIS_REPO) →_gh_move_tag "$host" "$agent/$frontier" "$cand"; this-repo agents (dev-lead, pr-review) keep the localgit tag -f+git push --force originpath — the same split canary-rollout evaluate mis-reports all 6 cross-repo (#482) reusables as 'fully rolled out' — reads local tags, not .github #1049 added for reads--dry-runhost-aware for the cross-repo casetests/canary_rollout.batsTest Results
Files Changed
scripts/canary-rollout.sh: new_gh_move_taghost-aware channel-move helper;cmd_promotenow moves the channel tag onhostvia the GitHub API for cross-repo agents instead of localgit tag -f/git push(which failed withnonexistent object/ wrong-repo, exit 128)tests/canary_rollout.bats:_crossrepo_promote_stub+ 2 tests. The git stub deliberately fails (exit 128) ontag -f/push, so a regression to the local path re-breaks the test; the gh stub logs the host API move and the test asserts it targetsgit/refs/tags/auto-rebase/stableat the candidate commitNotes
git tag -f/pushfail loudly and (b) asserting the loggedPATCH .../git/refs/tags/auto-rebase/stablecarries the candidate SHA — exactly the "must move the tag on.github, not fail on a nonexistent local object" case requested.cmd_rollbackhas the same local-git assumption (itsgit rev-parse refs/tags/$agent/$toread andgit tag -fwrite would also fail for a cross-repo agent), but that is out of scope here — worth a follow-up issue if cross-repo rollback becomes a requirement.feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
16 remaining items
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …
feat: implement issue #1054 — canary-rollout PROMOTE fails for cross-…
feat(canary-rollout): gated scheduled fleet auto-promote (#1045 part …