ci(port): track upstream HEAD, guard backwards ports, open PRs via GitHub App - #5
Merged
Merged
Conversation
…tHub App
Brings go-agent's port automation to parity with python-agent. The two ports share
one pipeline design, so the port *logic* should not diverge between them — a fix
found in one repo has to land in both or the ports drift out of sync.
1. Track upstream HEAD, not the latest npm release
The workflow curled registry.npmjs.org and ported the resolved release tag, so it
was structurally blind to unreleased work. Upstream currently sits 4 commits past
the 0.8.0 tag with the doom-loop change (~7.5k lines) unreleased; a release-tracking
port sees "no changes" and then absorbs the whole delta in one run.
Blank ref now means upstream default-branch HEAD. Only an explicit inputs.ref is
honored; the publish dispatch's client_payload.ref is deliberately ignored with a
::notice:: explaining why — it carries the release tag, which is an ancestor of
HEAD once the port is ahead, so honoring it would revert landed work.
2. Fixed the --ref "" bug
The Run port step passed --ref unconditionally. `--ref ""` is not the same as
omitting it: the arg parser consumes the empty value. Now only passed when
non-empty, with ${args[@]+"${args[@]}"} for safe empty expansion under set -u.
3. Refuse to port backwards
scripts/upstream exits 3 when the target is an ancestor of state.yaml's
upstream_commit, naming how many commits behind and pointing at --force. Verified
by pointing state at HEAD and targeting the 0.8.0 tag: refused, 4 commits behind.
4. Release-tag position (the Go analogue of Python's version-drift check)
This needed translation rather than copying. A Go module declares no version, so
there is no downstream version field to drift against upstream's package.json —
that check has no analogue here. What does apply is position relative to the
release tag: `git tag v0.8.0` on a HEAD-tracking tree would publish post-0.8.0
behavior under a released number, and a pushed tag is immutable in the module
proxy's cache. So the verifier reports how many commits ahead the tree is and warns
not to tag from that state. Not a failure — being ahead is the intended state.
5. verify-port is now blocking, plus advisory test-parity reporting
continue-on-error is gone. It was advisory while the port sat a version behind and
the required-API check failed by design; that closed with the 0.8.0 port, so
advisory only bought the ability to regress the parity floor with a green PR.
Test parity is reported by upstream test *cluster* rather than by filename: Go does
not keep a 1:1 file mapping with upstream's tests, so a filename heuristic would be
noise. Currently flags one real gap — shared-*.test.ts has no counterpart anywhere
in the Go suite.
6. Open port PRs with a GitHub App
Only pull_request-event runs satisfy required status checks, and GitHub does not
trigger workflows from GITHUB_TOKEN-created events, so a bot PR opened with it can
never merge. Measured on python-agent: 14 check-runs on the commit, 7 in the PR's
rollup — the dispatched half invisible to branch protection. An App installation
token is not recursion-guarded.
The mint step is gated on BOTH the App ID (variable) and key (secret), via a
job-level env boolean: the `secrets` context is NOT available in a step-level `if:`
and silently evaluates to empty there, which would make the gate never match. The
old dispatch step is retained as a fallback that warns loudly when no App token was
minted, rather than leaving a silently unmergeable PR.
Verification: verify.sh PASS (0 failures) — gofmt, build, vet, test, test -race,
24 required symbols, coverage 72.2% over a 72.0% floor, every required-API symbol
test-covered · both workflows parse · both shell scripts pass bash -n · backwards
guard and release-tag position each verified against a real upstream clone in both
states · state.yaml untouched.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings go-agent's port automation to parity with the changes landing in
python-agent(PR #24 there). Both ports run the same pipeline design, so the port logic must not diverge — a fix found in one repo has to land in both, or the two language ports drift apart.1. Track upstream HEAD, not the latest npm release
The workflow curled
registry.npmjs.organd ported the resolved release tag, making it structurally blind to unreleased work. Upstream currently sits 4 commits past the 0.8.0 tag with the doom-loop change (~7,500 lines) unreleased — a release-tracking port reports "no changes", then absorbs the whole delta in one run.Blank ref now means upstream default-branch HEAD. Only an explicit
inputs.refis honored; the publish dispatch'sclient_payload.refis deliberately ignored with a::notice::— it carries the release tag, an ancestor of HEAD once the port is ahead, so honoring it would revert landed work.2. Fixed the
--ref ""bugThe Run port step passed
--refunconditionally.--ref ""is not equivalent to omitting it — the arg parser consumes the empty value. Now only passed when non-empty, with${args[@]+"${args[@]}"}for safe empty expansion underset -u.3. Refuse to port backwards
scripts/upstreamexits 3 when the target is an ancestor ofstate.yaml'supstream_commit. Verified by pointing state at HEAD and targeting the 0.8.0 tag:4. Release-tag position — translated, not copied
This is where Go needed different thinking. python-agent compares
pyproject.tomlagainst upstream'spackage.json; a Go module declares no version, so that drift check has no analogue here and is deliberately absent.What does apply is position relative to the release tag:
git tag v0.8.0on a HEAD-tracking tree would publish post-0.8.0 behavior under a released number, and a pushed tag is immutable in the module proxy's cache — the Go equivalent of an irreversible PyPI publish. The verifier now reports how many commits ahead the tree is and warns not to tag from that state. Not a failure; being ahead is the intended state.Verified in both positions against a real upstream clone:
PASS: ported tree is level with the 0.8.0 release tag (taggable)NOTE: ... do NOT cut a v0.8.0 tag from this state— exit 05.
verify-portis blocking, plus advisory test-parity reportingcontinue-on-errorremoved. It was advisory while the port sat a version behind and the required-API check failed by design; that closed with the 0.8.0 port, so advisory only bought the ability to regress the parity floor with a green PR. Confirmed the verifier passes clean before making it blocking.Test parity is reported by upstream test cluster, not filename — Go keeps no 1:1 file mapping with upstream's tests, so a filename heuristic would be noise. It currently flags one real gap:
shared-*.test.tshas no counterpart anywhere in the Go suite.6. Open port PRs with a GitHub App
Only
pull_request-event runs satisfy required status checks, and GitHub doesn't trigger workflows fromGITHUB_TOKEN-created events — so a bot PR opened with it can never merge. Measured on python-agent: 14 check-runs on the commit, 7 in the PR's rollup; the dispatched half was invisible to branch protection.The mint step is gated on both the App ID (variable) and key (secret), via a job-level
envboolean — because thesecretscontext is NOT available in a step-levelif:and silently evaluates to empty there, which would make the gate never match. The old dispatch step is retained as a fallback that warns loudly when no App token was minted, rather than leaving a silently unmergeable PR.PORT_BOT_APP_IDandPORT_BOT_PRIVATE_KEYare both configured on this repo, so the App path is live.Verification
verify.shPASS (0 failures) — gofmt, build, vet, test,test -race, 24 required symbols, coverage 72.2% over a 72.0% floor, every required-API symbol test-covered · both workflows parse · both shell scripts passbash -n· backwards guard and release-tag position each verified against a real upstream clone in both states ·state.yamluntouched.Note on the next run
The next cron will see HEAD (4 commits ahead) and port the doom-loop delta — a large first delta, because it's the backlog release-tracking was hiding. Per the Test Parity rules already in this repo, it must port the upstream tests alongside the source, not just the source.
Follow-up: this repo's
mainis unprotectedmainhas no branch protection, so none of these gates block a merge yet. python-agent now requires six checks withenforce_admins. Worth applying the equivalent here — with the App token in place, automated port PRs will satisfy required checks correctly.🤖 Generated with Claude Code