@@ -485,43 +485,75 @@ jobs:
485485 permissions :
486486 contents : read
487487 steps :
488+ # Idempotency guard: a re-run of the release for a tag that was already
489+ # published must NOT re-trigger the downstream release. The desktop
490+ # release re-pushes docker/docker-model-cli-desktop-module:<tag>, and
491+ # pushing an already-existing tag fails with "403 denied". Detect the
492+ # already-published tag on Docker Hub and skip the trigger instead.
493+ - name : Log in to DockerHub
494+ uses : docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
495+ with :
496+ username : " docker"
497+ password : ${{ secrets.ORG_ACCESS_TOKEN }}
498+
499+ - name : Check whether desktop module is already published
500+ id : published
501+ env :
502+ RELEASE_TAG : ${{ needs.prepare.outputs.release_tag }}
503+ run : |
504+ if docker manifest inspect \
505+ "docker/docker-model-cli-desktop-module:$RELEASE_TAG" >/dev/null 2>&1; then
506+ echo "already=true" >> "$GITHUB_OUTPUT"
507+ echo "✅ desktop-module:$RELEASE_TAG already exists — skipping trigger"
508+ else
509+ echo "already=false" >> "$GITHUB_OUTPUT"
510+ fi
511+
488512 - name : Trigger Desktop CLI release and wait for completion
513+ if : steps.published.outputs.already != 'true'
489514 env :
490515 GH_TOKEN : ${{ secrets.CLI_RELEASE_PAT }}
491516 RELEASE_TAG : ${{ needs.prepare.outputs.release_tag }}
492517 VERSION : ${{ needs.prepare.outputs.version }}
493518 run : |
494519 echo "🚀 Triggering Desktop CLI release workflow"
495520
496- # gh workflow run returns run URL on stdout (gh v2.87.0+)
497- OUTPUT=$(gh workflow run release-cli-dd.yml \
521+ # Record the newest existing run BEFORE dispatch so we can identify
522+ # the run WE trigger. `gh workflow run` prints nothing usable on
523+ # stdout, and "query the latest run" races: it can return a run
524+ # started by someone else, or a stale completed run (which is how a
525+ # trigger job can "succeed" while watching the wrong run). Run IDs are
526+ # monotonically increasing, so the first run with a strictly greater
527+ # ID than BEFORE_ID is ours.
528+ BEFORE_ID=$(gh run list \
498529 --repo docker/inference-engine-llama.cpp \
499- -f model -cli-ref="$RELEASE_TAG" \
500- -f tag="v$VERSION" )
530+ --workflow release -cli-dd.yml \
531+ --limit 1 --json databaseId --jq '.[0].databaseId // 0' )
501532
502- RUN_URL=$(echo "$OUTPUT" \
503- | grep -o 'https://github.com/[^ ]*/actions/runs/[0-9]*') || true
533+ gh workflow run release-cli-dd.yml \
534+ --repo docker/inference-engine-llama.cpp \
535+ -f model-cli-ref="$RELEASE_TAG" \
536+ -f tag="v$VERSION"
504537
505- if [ -z "$RUN_URL" ]; then
506- echo "⚠️ Could not extract run URL from gh output, querying latest run..."
507- sleep 5
508- RUN_ID =$(gh run list \
538+ RUN_ID=""
539+ for _ in $(seq 1 30); do
540+ sleep 3
541+ CANDIDATE =$(gh run list \
509542 --repo docker/inference-engine-llama.cpp \
510543 --workflow release-cli-dd.yml \
511- --limit 1 \
512- --json databaseId \
513- --jq '.[0].databaseId')
514- else
515- RUN_ID=$(echo "$RUN_URL" | grep -o '[0-9]*$')
516- fi
544+ --limit 1 --json databaseId --jq '.[0].databaseId // 0')
545+ if [ "$CANDIDATE" -gt "$BEFORE_ID" ]; then
546+ RUN_ID="$CANDIDATE"
547+ break
548+ fi
549+ done
517550
518551 if [ -z "$RUN_ID" ]; then
519- echo "::error::Failed to determine workflow run ID"
552+ echo "::error::Failed to determine Desktop CLI release run ID"
520553 exit 1
521554 fi
522555
523556 echo "::add-mask::$RUN_ID"
524- echo "::add-mask::$RUN_URL"
525557 echo "✅ Desktop CLI release workflow triggered"
526558
527559 echo "⏳ Waiting for Desktop CLI release to complete..."
@@ -556,34 +588,39 @@ jobs:
556588 run : |
557589 echo "📦 Triggering packaging workflow"
558590
559- # gh workflow run returns run URL on stdout (gh v2.87.0+)
560- OUTPUT=$(gh workflow run release-model.yml \
591+ # Identify the run WE trigger by its ID, not by "the latest run".
592+ # `gh workflow run` prints nothing usable on stdout and querying the
593+ # newest run right after dispatch races with concurrent/stale runs.
594+ # Run IDs increase monotonically, so the first run with an ID greater
595+ # than the pre-dispatch newest is ours. (See release-cli-desktop.)
596+ BEFORE_ID=$(gh run list \
561597 --repo docker/packaging \
562- -f ref="$RELEASE_TAG")
598+ --workflow release-model.yml \
599+ --limit 1 --json databaseId --jq '.[0].databaseId // 0')
563600
564- RUN_URL=$(echo "$OUTPUT" \
565- | grep -o 'https://github.com/[^ ]*/actions/runs/[0-9]*') || true
601+ gh workflow run release-model.yml \
602+ --repo docker/packaging \
603+ -f ref="$RELEASE_TAG"
566604
567- if [ -z "$RUN_URL" ]; then
568- echo "⚠️ Could not extract run URL from gh output, querying latest run..."
569- sleep 5
570- RUN_ID =$(gh run list \
605+ RUN_ID=""
606+ for _ in $(seq 1 30); do
607+ sleep 3
608+ CANDIDATE =$(gh run list \
571609 --repo docker/packaging \
572610 --workflow release-model.yml \
573- --limit 1 \
574- --json databaseId \
575- --jq '.[0].databaseId')
576- else
577- RUN_ID=$(echo "$RUN_URL" | grep -o '[0-9]*$')
578- fi
611+ --limit 1 --json databaseId --jq '.[0].databaseId // 0')
612+ if [ "$CANDIDATE" -gt "$BEFORE_ID" ]; then
613+ RUN_ID="$CANDIDATE"
614+ break
615+ fi
616+ done
579617
580618 if [ -z "$RUN_ID" ]; then
581619 echo "::error::Failed to determine packaging workflow run ID"
582620 exit 1
583621 fi
584622
585623 echo "::add-mask::$RUN_ID"
586- echo "::add-mask::$RUN_URL"
587624 echo "✅ Packaging workflow triggered"
588625
589626 echo "⏳ Waiting for packaging to complete..."
@@ -662,6 +699,12 @@ jobs:
662699 RELEASE_TAG : ${{ needs.prepare.outputs.release_tag }}
663700 VERSION : ${{ needs.prepare.outputs.version }}
664701 run : |
702+ # Idempotency: a re-run for an already-released tag must not fail here.
703+ if gh release view "$RELEASE_TAG" --repo docker/model-runner >/dev/null 2>&1; then
704+ echo "✅ GitHub Release $RELEASE_TAG already exists — skipping creation"
705+ exit 0
706+ fi
707+
665708 echo "Creating GitHub Release for $RELEASE_TAG"
666709 gh release create "$RELEASE_TAG" \
667710 --repo docker/model-runner \
0 commit comments