@@ -181,30 +181,43 @@ jobs:
181181 AGENT_NAME="${{ steps.image-name.outputs.agent_name }}"
182182 REPOSITORY_NAME="${{ github.repository }}/tutorial-agents/${AGENT_NAME}"
183183
184- # Determine if we should push based on event type
184+ # Determine if we should publish based on event type.
185+ # Publish path: push to an immutable candidate tag (the commit SHA) first,
186+ # validate that exact pushed artifact, then promote :latest onto it. This
187+ # keeps an unvalidated image off :latest — if validation fails, :latest is
188+ # left pointing at the last known-good build, and only the SHA tag is dirty.
185189 if [ "${{ github.event_name }}" = "push" ] || [ "${{ inputs.rebuild_all }}" = "true" ]; then
186190 SHOULD_PUSH=true
187- VERSION_TAG="latest"
188- echo "🚀 Building agent (will push after validation): ${{ matrix.agent_path }}"
191+ PROMOTE_LATEST=true
192+ VERSION_TAG="${{ github.sha }}"
193+ echo "🚀 Building agent (push candidate ${VERSION_TAG}, promote :latest after validation): ${{ matrix.agent_path }}"
189194 else
190195 SHOULD_PUSH=false
196+ PROMOTE_LATEST=false
191197 VERSION_TAG="${{ github.sha }}"
192198 echo "🔍 Building agent for validation: ${{ matrix.agent_path }}"
193- # Set full image name for validation step (local build)
194- echo "FULL_IMAGE=${REGISTRY}/${REPOSITORY_NAME}:${VERSION_TAG}" >> $GITHUB_ENV
195199 # Skip image validation for PRs since Buildx doesn't load multi-platform images locally
196200 echo "SKIP_VALIDATION=true" >> $GITHUB_ENV
197201 fi
198202
199- # Always build locally first (without push)
203+ # Build the image. On the publish path, push straight from buildx to the
204+ # candidate tag: a multi-platform build cannot be loaded into the local
205+ # Docker store, so it must be pushed by the build itself rather than by a
206+ # later `docker push` (which would have no fresh local image and would
207+ # re-push a stale tag instead).
200208 BUILD_ARGS="--manifest ${{ matrix.agent_path }}/manifest.yaml --registry ${REGISTRY} --tag ${VERSION_TAG} --platforms linux/amd64,linux/arm64 --repository-name ${REPOSITORY_NAME}"
209+ if [ "$SHOULD_PUSH" = "true" ]; then
210+ BUILD_ARGS="$BUILD_ARGS --push"
211+ fi
201212
202213 agentex agents build $BUILD_ARGS
203214 echo "✅ Successfully built: ${REGISTRY}/${REPOSITORY_NAME}:${VERSION_TAG}"
204215
205216 # Set environment variables for subsequent steps
206217 echo "FULL_IMAGE=${REGISTRY}/${REPOSITORY_NAME}:${VERSION_TAG}" >> $GITHUB_ENV
218+ echo "LATEST_IMAGE=${REGISTRY}/${REPOSITORY_NAME}:latest" >> $GITHUB_ENV
207219 echo "SHOULD_PUSH=${SHOULD_PUSH}" >> $GITHUB_ENV
220+ echo "PROMOTE_LATEST=${PROMOTE_LATEST}" >> $GITHUB_ENV
208221
209222 - name : Validate agent image
210223 if : env.SKIP_VALIDATION != 'true'
@@ -279,13 +292,15 @@ jobs:
279292
280293 echo "✅ All validations passed for: $FULL_IMAGE"
281294
282- - name : Push Agent Image
283- if : env.SHOULD_PUSH == 'true'
295+ - name : Promote validated image to :latest
296+ if : env.PROMOTE_LATEST == 'true'
284297 run : |
285- FULL_IMAGE="${{ env.FULL_IMAGE }}"
286- echo "🚀 Pushing validated image: $FULL_IMAGE"
287- docker push "$FULL_IMAGE"
288- echo "✅ Successfully pushed: $FULL_IMAGE"
298+ echo "🏷️ Promoting validated ${{ env.FULL_IMAGE }} -> ${{ env.LATEST_IMAGE }}"
299+ # Registry-side manifest copy: no rebuild, preserves the multi-arch
300+ # manifest list, and only runs after validation passed — so :latest never
301+ # points at an unvalidated image.
302+ docker buildx imagetools create --tag "${{ env.LATEST_IMAGE }}" "${{ env.FULL_IMAGE }}"
303+ echo "✅ Promoted to ${{ env.LATEST_IMAGE }}"
289304
290305 deprecate-agents :
291306 name : " Deprecate Removed Agents"
0 commit comments