ci(release): dispatch the Python and Go ports on publish (HOP C) - #84
Conversation
The Python and Go ports of @openrouter/agent track this repo as their reference spec, but nothing told them when a version shipped. Both were generated by hand against 0.7.2 and have sat a minor version behind since — missing HooksManager, versioned state serialization, and the #61-#68 fixes. Adds a HOP C dispatch beside the existing HOP B monorepo dispatch. On a real publish, python-agent and go-agent each receive openrouter-agent-published and open a PR porting the delta. Their pipelines gate the result on a mechanical verifier plus a behavioral parity eval before advancing sync state, so a bad port cannot land silently. Details worth noting: - Sends the release TAG (@openrouter/agent@X.Y.Z), not a branch, so a port reproduces the exact published tree rather than whatever main drifted to. - continue-on-error: the packages are already on npm when this runs, so a failed dispatch must not turn a successful release red. It warns instead, and names the manual recovery path. - !cancelled() so a failed HOP B dispatch doesn't also skip the ports. - Partial failure is tolerated: one port failing still dispatches the other. Reuses the same GH_TOKEN PAT as HOP B, which additionally needs contents:write on OpenRouterTeam/python-agent and OpenRouterTeam/go-agent. Also documents the full publish fan-out in the changeset-versioning skill, since a breaking callModel change now produces port PRs in two other repos. Companion PRs: OpenRouterTeam/python-agent#19, OpenRouterTeam/go-agent#1
There was a problem hiding this comment.
Summary
Appends a HOP C repository_dispatch step to publish.yaml notifying python-agent/go-agent on a real publish, plus docs in the changeset-versioning skill. Structure and failure isolation (continue-on-error, !cancelled(), per-repo tolerance) are right, but the ref payload is passed with gh api -F, whose @-prefix magic will make every dispatch fail silently, and on the manual publish path the tag it names is never pushed.
Findings (4)
🔴 critical · .github/workflows/publish.yaml:189
-F "client_payload[ref]=${TAG}" — gh api --field interprets a value starting with @ as a filename to read. TAG is @openrouter/agent@X.Y.Z, so gh errors out before sending the request and both dispatches take the failure branch; combined with continue-on-error + exit 0 the whole hop is a silent no-op. Use -f/--raw-field for that field.
🟠 major · .github/workflows/publish.yaml:182
ref is set to the changesets git tag, but the manual mode=publish path (pnpm exec changeset publish --no-git-checks, publish.yaml:97) only creates tags locally and nothing pushes them, while steps.published.outputs.version is still populated via the npm version diff. HOP C then dispatches a tag the port repos cannot resolve. Push tags on that path, or fall back to github.sha when git ls-remote --tags misses.
🟡 minor · .github/workflows/publish.yaml:181
set -uo pipefail drops -e (needed for the explicit if gh api check), so any other command failure in the body is silently ignored. Scoping the relaxed mode to just the gh call (e.g. keep set -euo pipefail and use if ! gh api ... ; then — which is already -e-exempt) would keep the rest of the script strict.
⚪ nit · .github/workflows/publish.yaml:190
Mixed interpolation styles: VERSION is passed via env: (correct, avoids injection) while github.server_url/github.run_id are inlined as ${{ }} in the shell body. Harmless for these values but inconsistent with the env-var approach used one line above.
- Use -f (--raw-field) for all dispatch payload fields: -F treats
values starting with @ (the release tag) as filenames, which made
every port dispatch fail before the request was sent
- Push release tags on the manual mode=publish path so the dispatched
ref actually exists on the remote
- Restore set -e; the gh api call sits in an if-condition and is
already -e-exempt, so the rest of the script stays strict
- Pass source_run_url via env like VERSION instead of inline ${{ }}
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed all 4 review findings in d259bfc:
|
There was a problem hiding this comment.
Summary
The update fixes both blocking findings from the previous review: all dispatch fields now use -f/--raw-field (publish.yaml:186-189), set -euo pipefail is restored, source_run_url moved to env:, and a tag-push step was added for the manual publish path. The tag-push fix itself introduces a smaller regression: it is a fatal step placed after npm publish, so a rejected push reddens a successful release and suppresses both HOP B and HOP C.
Findings (3)
🟠 major · .github/workflows/publish.yaml:96-98
NEW: Push release tags (manual publish) runs after changeset publish with no continue-on-error. A rejected push (tag protection ruleset, or a re-run where the tag already exists at a different commit) fails the job post-npm-publish and, because Resolve published @openrouter/agent version and Dispatch monorepo bump have no status-check if:, leaves steps.published.outputs.version empty so HOP B and HOP C are both skipped despite !cancelled(). Add continue-on-error: true (optionally with the git ls-remote tag-existence guard + github.sha fallback for ref).
🟡 minor · .github/workflows/publish.yaml:93-95
RESOLVED (partially, see above) — prior finding that ref named an unpushed tag on the manual publish path is addressed by the new push step; residual gap is only the failure mode above, i.e. ref still has no fallback if the push doesn't land.
🟡 minor · .github/workflows/publish.yaml:186-189
RESOLVED — prior critical: dispatch fields switched from -F to -f, so @openrouter/agent@X.Y.Z is no longer interpreted as a filename; the inline comment records why. Also resolved: set -euo pipefail restored (publish.yaml:181) and SOURCE_RUN_URL moved into env: instead of inline ${{ }}.
| if gh api "repos/OpenRouterTeam/${REPO}/dispatches" \ | ||
| -f event_type=openrouter-agent-published \ | ||
| -f "client_payload[version]=${VERSION}" \ | ||
| -f "client_payload[ref]=${TAG}" \ | ||
| -f "client_payload[source_run_url]=${SOURCE_RUN_URL}"; then |
There was a problem hiding this comment.
🔍 Nested client_payload[...] bracket syntax with gh api
HOP C builds the dispatch payload with -f 'client_payload[version]=...', -f 'client_payload[ref]=...', etc. This mirrors the pre-existing HOP B step (.github/workflows/publish.yaml:152-155), which uses the same bracket notation. Whether gh api expands client_payload[key]=value into a nested JSON object versus a flat top-level key literally named client_payload[key] depends on the gh CLI version's behavior. Since HOP B already relies on this and is presumably verified in production, HOP C is consistent with it and I did not flag it — but if HOP B were ever found to send a flat/literal key, HOP C would inherit the same issue for the ref payload the ports depend on.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Checked this empirically rather than leaving it as an open question — the bracket notation does nest correctly.
Ran the same call shape against gh 2.96.0 with GH_DEBUG=api and the serialized request body was:
{
"client_payload": { "ref": "@openrouter/agent@1.2.3", "version": "1.2.3" },
"event_type": "..."
}So -f 'client_payload[key]=value' expands into a nested object, not a flat literal key. HOP B's production behavior and HOP C's ref payload are both fine on this axis. Leaving the thread open for a human to resolve since it was a question rather than a defect.
Worth noting your -f vs -F distinction still matters and is handled: ref starts with @, so -F would have treated it as a filename. HOP C uses -f throughout.
…origin Address review findings from cortex and Devin on the HOP C dispatch: - Make the manual-publish tag push non-fatal. It runs after packages are already on npm, so a rejected push (tag protection, or a re-run where the tag exists at a different commit) turned a successful publish red and, because the HOP B/C steps carry no status guard, skipped both dispatches. - Verify the tag is on origin before dispatching it, falling back to the run's commit SHA. Previously the manual path could dispatch a ref the port repos cannot resolve, making them fail on checkout instead of degrading.
# Conflicts: # .agents/skills/changeset-versioning/SKILL.md
There was a problem hiding this comment.
Summary
All three prior findings are addressed: dispatch fields use -f so the @-prefixed tag isn't read as a file, the manual-path tag push is now continue-on-error: true (publish.yaml:148-150) so it can't redden a successful publish or suppress HOP B/C, and HOP C verifies the tag is on origin with a github.sha fallback (publish.yaml:250-256) before dispatching. Remaining risk is limited to the cross-repo receiving side, which isn't in this repo.
Findings (1)
⚪ nit · .github/workflows/publish.yaml:253
The github.sha fallback is only useful if the port repos' checkout of this repo can resolve an arbitrary commit SHA (GitHub supports fetch-by-SHA, but a shallow branch clone plus a bare git checkout <sha> will not). Worth confirming once against python-agent#19 / go-agent#1 since the fallback path is exactly the one nobody exercises until it matters.
|
@devin please re-review — addressed your findings and cortex's in
Also merged main (which brought in OIDC trusted publishing). |
|
Could not match GitHub user LukasParke to a Devin account. Please make sure your GitHub account is linked in your Devin settings. |
Review of
|
Adds a HOP C dispatch so a published
@openrouter/agenttells its Python and Go ports to sync.Third of three PRs:
Why
python-agentandgo-agenttreat this repo as their reference spec, but nothing ever told them a version shipped. Both were generated by hand against 0.7.2 and have sat a minor version behind ever since — missingHooksManager(#7, #67), versioned state serialization (#66), and the #61–#68 fixes. The existing release chain deliberately routes around them:What it does
One step after the existing HOP B dispatch. On a real publish, both port repos receive
openrouter-agent-publishedand each opens a PR porting the delta, gated on a mechanical verifier plus a behavioral parity eval before their sync state advances.Four details that matter
Sends the release tag, not a branch.
client_payload[ref]is@openrouter/agent@X.Y.Z, so a port reproduces the exact published tree rather than whatevermaindrifted to afterwards. Verified this matches the tag format changesets actually creates in this repo.continue-on-error: true— cannot fail the release. By the time this step runs, the packages are already on npm. A red job here would misreport a successful publish. It warns and names the recovery path (run the port repo's Upstreamer Port workflow manually with that tag, or wait for its weekly cron).!cancelled()so it's independent of HOP B. A failed monorepo dispatch shouldn't also stop the ports from being notified.Partial failure tolerated. One port failing to dispatch still dispatches the other; both are named in the warning.
Token requirement
Reuses the same
GH_TOKENPAT as HOP B. It additionally needscontents:writeonOpenRouterTeam/python-agentandOpenRouterTeam/go-agent. If that scope isn't added, this step warns and the ports fall back to their weekly cron — no release breakage either way, but the fast path won't work until the PAT covers both repos.Conflict with #82
#82 also edits
publish.yaml, but its hunks stop just before the dispatch region (its last change is removingNODE_AUTH_TOKENfrom the publish steps). This adds only appended steps after HOP B, so they should merge cleanly in either order. Happy to rebase behind #82 if you'd prefer it lands first.Also
Documents the full publish fan-out in
.agents/skills/changeset-versioning/SKILL.md. Worth a read in review: a breakingcallModelchange now produces port PRs in two other repos, which is a new consequence of releasing here.Verified
bash -nclient_payload[ref]correctly: a dispatch with no manual inputs resolves to./scripts/upstream --ref @openrouter/agent@0.8.0@openrouter/agent@0.8.0resolves to a real commit in this repoNothing here runs until the next publish, and the first port runs are intended to be driven manually anyway.