Skip to content

Commit c4eb125

Browse files
authored
Merge pull request #7149 from JSONbored/fix/release-please-reconcile-stale-tagging
fix(release): self-heal the release-please "untagged, merged" abort
2 parents c060b2a + 240f29c commit c4eb125

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

.github/workflows/mcp-release-please.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,127 @@ jobs:
192192
env:
193193
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
194194
run: gh workflow run publish-ui-kit.yml --ref "${{ steps.release.outputs['packages/loopover-ui-kit--tag_name'] }}" -f released_by_release_please=true
195+
196+
# Self-heal for a known, reproducible, upstream googleapis/release-please limitation
197+
# (googleapis/release-please#1946, #1444, #1406 -- all the same "There are untagged, merged release
198+
# PRs outstanding - aborting" message, unresolved upstream): createReleases() (which actually tags a
199+
# merged Release PR) sometimes finds nothing to do, and createPullRequests() then aborts on every
200+
# subsequent run without ever tagging the merged PR -- confirmed live across two separate
201+
# linked-versions release cycles this session (#7127, #7133), both requiring a manual dispatch of
202+
# each publish-*.yml workflow plus manually flipping the merged PR's `autorelease: pending` label to
203+
# `autorelease: tagged` to unstick it. Traced as far as release-please's own source
204+
# (manifest.ts/workspace.ts) allows without being able to modify the third-party action itself.
205+
#
206+
# Rather than depend on release-please's own `steps.release.outputs` above (unreliable exactly when
207+
# this bug is in play, since createReleases() never actually tagged anything), this reconciles from
208+
# the OTHER direction: compare each package's committed package.json version against what's
209+
# ACTUALLY live on npm, and self-heal by dispatching that package's publish workflow directly
210+
# whenever they disagree -- the same "bare manual dispatch" human-override path each publish-*.yml
211+
# already documents, just triggered automatically instead of by a human noticing red CI on an
212+
# unrelated PR days later. Runs on every push (cheap: a handful of `npm view` calls, no-op when
213+
# everything's already in sync), independent of whether the release-please job above succeeded,
214+
# aborted, or hit a transient error.
215+
#
216+
# Engine publishes first and is awaited before mcp/miner: they carry a real runtime dependency on
217+
# it, and their own isolated pack/smoke-test step resolves @loopover/engine from the real npm
218+
# registry (no local workspace symlink available in that isolated temp dir), so it fails with
219+
# ETARGET if engine hasn't actually finished publishing yet (confirmed live: the first attempt at
220+
# this exact reconciliation, dispatched in parallel, hit that exact race).
221+
reconcile-stale-releases:
222+
runs-on: ubuntu-latest
223+
needs: release-please
224+
if: ${{ !cancelled() }}
225+
environment: release
226+
timeout-minutes: 25
227+
permissions:
228+
contents: read
229+
actions: write
230+
pull-requests: write
231+
steps:
232+
- name: Checkout
233+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
234+
with:
235+
persist-credentials: false
236+
- name: Setup Node
237+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
238+
with:
239+
node-version: 24.18.0
240+
- name: Publish any package whose committed version isn't live on npm yet
241+
env:
242+
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
243+
run: |
244+
set -euo pipefail
245+
246+
needs_publish() {
247+
local dir="$1" pkg="$2"
248+
local local_version published_version
249+
local_version="$(node -p "require('./$dir/package.json').version")"
250+
published_version="$(npm view "$pkg" version 2>/dev/null || echo "")"
251+
if [ "$local_version" != "$published_version" ]; then
252+
echo "$pkg: committed $local_version, npm has ${published_version:-<none>}."
253+
return 0
254+
fi
255+
echo "$pkg: already in sync at $local_version."
256+
return 1
257+
}
258+
259+
dispatch_and_wait() {
260+
local workflow="$1"
261+
gh workflow run "$workflow" --repo "$GITHUB_REPOSITORY"
262+
sleep 8
263+
local run_id
264+
run_id="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --limit 1 --json databaseId --jq '.[0].databaseId')"
265+
echo "Dispatched $workflow as run $run_id, waiting for it to complete..."
266+
while [ "$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json status --jq '.status')" != "completed" ]; do
267+
sleep 15
268+
done
269+
local conclusion
270+
conclusion="$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json conclusion --jq '.conclusion')"
271+
echo "$workflow (run $run_id) concluded: $conclusion"
272+
[ "$conclusion" = "success" ]
273+
}
274+
275+
engine_ok=true
276+
if needs_publish packages/loopover-engine @loopover/engine; then
277+
dispatch_and_wait publish-engine.yml || engine_ok=false
278+
fi
279+
280+
if [ "$engine_ok" = "true" ]; then
281+
if needs_publish packages/loopover-mcp @loopover/mcp; then
282+
dispatch_and_wait publish-mcp.yml || echo "::warning::publish-mcp.yml did not succeed -- left for manual follow-up."
283+
fi
284+
if needs_publish packages/loopover-miner @loopover/miner; then
285+
dispatch_and_wait publish-miner.yml || echo "::warning::publish-miner.yml did not succeed -- left for manual follow-up."
286+
fi
287+
else
288+
echo "::warning::Skipping mcp/miner reconciliation -- publish-engine.yml did not succeed, and they depend on it."
289+
fi
290+
291+
if needs_publish packages/loopover-ui-kit @loopover/ui-kit; then
292+
dispatch_and_wait publish-ui-kit.yml || echo "::warning::publish-ui-kit.yml did not succeed -- left for manual follow-up."
293+
fi
294+
295+
# Once every committed version is confirmed live on npm (the loop above didn't need to run, or
296+
# completed successfully for everything it dispatched), any merged Release PR still carrying
297+
# release-please's own `autorelease: pending` label is exactly the stuck state this job exists
298+
# to fix -- flip it so release-please's NEXT run stops aborting on it.
299+
- name: Un-stick release-please's own tracking on any merged-but-still-pending release PR
300+
env:
301+
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
302+
run: |
303+
set -euo pipefail
304+
for dir_pkg in "packages/loopover-engine:@loopover/engine" "packages/loopover-mcp:@loopover/mcp" "packages/loopover-miner:@loopover/miner" "packages/loopover-ui-kit:@loopover/ui-kit"; do
305+
dir="${dir_pkg%%:*}"
306+
pkg="${dir_pkg##*:}"
307+
local_version="$(node -p "require('./$dir/package.json').version")"
308+
published_version="$(npm view "$pkg" version 2>/dev/null || echo "")"
309+
if [ "$local_version" != "$published_version" ]; then
310+
echo "::warning::$pkg still not live on npm (committed $local_version, npm has ${published_version:-<none>}) -- leaving autorelease labels alone."
311+
exit 0
312+
fi
313+
done
314+
numbers="$(gh pr list --repo "$GITHUB_REPOSITORY" --state merged --label "autorelease: pending" --json number --jq '.[].number')"
315+
for number in $numbers; do
316+
echo "Flipping autorelease label on merged PR #$number -- its packages are confirmed live on npm."
317+
gh pr edit "$number" --repo "$GITHUB_REPOSITORY" --remove-label "autorelease: pending" --add-label "autorelease: tagged"
318+
done

0 commit comments

Comments
 (0)