From c2f6fea667d5c158128b00bfb4820929cee95341 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:50:09 +0000 Subject: [PATCH 1/5] Initial plan From a6487f14ab8a2415046db5494a50e5b2563d4b35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:08:47 +0000 Subject: [PATCH 2/5] Alias downloaded Docker images with :latest tag to fix cli-proxy tag mismatch Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/sh/download_docker_images.sh | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/actions/setup/sh/download_docker_images.sh b/actions/setup/sh/download_docker_images.sh index 48213a0c522..e269d530ba3 100755 --- a/actions/setup/sh/download_docker_images.sh +++ b/actions/setup/sh/download_docker_images.sh @@ -15,6 +15,13 @@ set +o histexpand # When images include a digest pin (e.g. image:tag@sha256:abc), the script # ensures the tag alias (image:tag) is created after pulling so that tools # referencing images by tag (such as AWF with --skip-pull) can find them. +# +# The script also aliases the image as "image:latest" so that any downstream +# tool that references the image via the mutable ":latest" tag (regardless of +# which versioned tag was actually pulled) can still resolve it locally under +# --pull-never/--skip-pull semantics. This guards against tag mismatches +# between the version-pinned tag written here and a ":latest" reference used +# elsewhere (see gh-aw#50681). set -euo pipefail @@ -33,11 +40,29 @@ docker_pull_with_retry() { # When pulling with a digest pin (image:tag@sha256:...), Docker may not # create the tag alias automatically. Ensure the tag exists so that # downstream tools (e.g. AWF --skip-pull) can find the image by tag. + local tag_ref="$image" if [[ "$image" == *"@sha256:"* ]]; then - local tag_ref="${image%%@sha256:*}" + tag_ref="${image%%@sha256:*}" if [[ "$tag_ref" == *":"* ]]; then echo "Tagging digest-pinned image as $tag_ref" docker tag "$image" "$tag_ref" + else + tag_ref="$image" + fi + fi + + # Also alias the image under the mutable ":latest" tag whenever the + # pulled/tagged reference uses a versioned tag. This protects against + # tools that resolve the image via ":latest" under --pull-never/ + # --skip-pull semantics while the download step only wrote the + # version-pinned tag (see gh-aw#50681). + if [[ "$tag_ref" == *":"* && "$tag_ref" != *"@"* ]]; then + local repo_ref="${tag_ref%%:*}" + local tag_part="${tag_ref##*:}" + if [[ "$tag_part" != "latest" ]]; then + local latest_ref="${repo_ref}:latest" + echo "Aliasing $tag_ref as $latest_ref" + docker tag "$tag_ref" "$latest_ref" fi fi From 98e99d84f992df8f4051c660cd414205cfc9d501 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:09:15 +0000 Subject: [PATCH 3/5] Add changeset for cli-proxy tag mismatch fix Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .changeset/patch-fix-cli-proxy-latest-tag-alias.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-fix-cli-proxy-latest-tag-alias.md diff --git a/.changeset/patch-fix-cli-proxy-latest-tag-alias.md b/.changeset/patch-fix-cli-proxy-latest-tag-alias.md new file mode 100644 index 00000000000..c09907a737a --- /dev/null +++ b/.changeset/patch-fix-cli-proxy-latest-tag-alias.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Fix fleet-wide smoke-test outage caused by a Docker image tag mismatch: `download_docker_images.sh` now aliases every pulled image under the mutable `:latest` tag in addition to its version-pinned tag, so `docker compose up -d --pull never` can resolve `ghcr.io/github/gh-aw-firewall/cli-proxy` (and other AWF images) regardless of which tag they are referenced by. From a789fc687cbf6e2b3089a4a1a6db0b06dd3a251e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:40:40 +0000 Subject: [PATCH 4/5] fix: tighten AWF docker latest aliasing Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/sh/download_docker_images.sh | 33 ++++---- .../setup/sh/download_docker_images_test.sh | 79 ++++++++++++++++--- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/actions/setup/sh/download_docker_images.sh b/actions/setup/sh/download_docker_images.sh index e269d530ba3..5d06e692dab 100755 --- a/actions/setup/sh/download_docker_images.sh +++ b/actions/setup/sh/download_docker_images.sh @@ -37,26 +37,31 @@ docker_pull_with_retry() { if timeout 5m docker pull --quiet "$image" 2>&1; then echo "Successfully pulled $image" - # When pulling with a digest pin (image:tag@sha256:...), Docker may not - # create the tag alias automatically. Ensure the tag exists so that - # downstream tools (e.g. AWF --skip-pull) can find the image by tag. + # When pulling with a digest pin, Docker may not create a digest-free + # alias automatically. Preserve the original base reference and add back + # its implicit ":latest" or explicit tag alias so downstream tools can + # resolve the image locally under --pull-never/--skip-pull semantics. local tag_ref="$image" + local base_ref="$image" if [[ "$image" == *"@sha256:"* ]]; then - tag_ref="${image%%@sha256:*}" - if [[ "$tag_ref" == *":"* ]]; then - echo "Tagging digest-pinned image as $tag_ref" - docker tag "$image" "$tag_ref" + base_ref="${image%%@sha256:*}" + tag_ref="$base_ref" + if [[ "$base_ref" == *":"* ]]; then + echo "Tagging digest-pinned image as $base_ref" + docker tag "$image" "$base_ref" else - tag_ref="$image" + local latest_ref="${base_ref}:latest" + echo "Tagging digest-pinned image as $latest_ref" + docker tag "$image" "$latest_ref" + tag_ref="$latest_ref" fi fi - # Also alias the image under the mutable ":latest" tag whenever the - # pulled/tagged reference uses a versioned tag. This protects against - # tools that resolve the image via ":latest" under --pull-never/ - # --skip-pull semantics while the download step only wrote the - # version-pinned tag (see gh-aw#50681). - if [[ "$tag_ref" == *":"* && "$tag_ref" != *"@"* ]]; then + # Only AWF images need a mutable ":latest" alias for local compose + # stacks, and only when the requested reference was version-pinned. This + # avoids races when unrelated repositories are pulled concurrently with + # multiple distinct tags. + if [[ "$base_ref" == ghcr.io/github/gh-aw-* && "$tag_ref" == *":"* ]]; then local repo_ref="${tag_ref%%:*}" local tag_part="${tag_ref##*:}" if [[ "$tag_part" != "latest" ]]; then diff --git a/actions/setup/sh/download_docker_images_test.sh b/actions/setup/sh/download_docker_images_test.sh index 1416e370b25..f9eb1946877 100755 --- a/actions/setup/sh/download_docker_images_test.sh +++ b/actions/setup/sh/download_docker_images_test.sh @@ -50,10 +50,66 @@ else fi echo "" -# Test 3: Already cached images (should be fast) -echo -e "${YELLOW}Test 3: Already cached images${NC}" +# Test 3: AWF digest-pinned images restore local aliases without retagging unrelated latest refs +echo -e "${YELLOW}Test 3: Digest-pinned AWF alias handling${NC}" +WORKDIR=$(mktemp -d) +cleanup_mock_docker() { + rm -rf "$WORKDIR" +} +cat > "$WORKDIR/docker" <<'MOCK' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$*" >> "$DOCKER_LOG" +case "$1" in + pull) + exit 0 + ;; + tag) + exit 0 + ;; + *) + echo "unexpected docker command: $*" >&2 + exit 1 + ;; +esac +MOCK +chmod +x "$WORKDIR/docker" +DOCKER_LOG="$WORKDIR/docker.log" +export DOCKER_LOG +NODE_DIGEST="$(printf 'a%.0s' {1..64})" +CLI_PROXY_DIGEST="$(printf 'b%.0s' {1..64})" +if PATH="$WORKDIR:$PATH" bash "$DOWNLOAD_SCRIPT" \ + "ghcr.io/github/gh-aw-node@sha256:${NODE_DIGEST}" \ + "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:${CLI_PROXY_DIGEST}" \ + alpine:3.17 > /tmp/test3.log 2>&1; then + if grep -q 'tag ghcr.io/github/gh-aw-node@sha256:' "$DOCKER_LOG" \ + && grep -q 'ghcr.io/github/gh-aw-node:latest' "$DOCKER_LOG" \ + && grep -q 'tag ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:' "$DOCKER_LOG" \ + && grep -q 'ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44 ghcr.io/github/gh-aw-firewall/cli-proxy:latest' "$DOCKER_LOG"; then + echo -e "${GREEN}✓ PASS${NC}: AWF digest aliases restored correctly" + else + echo -e "${RED}✗ FAIL${NC}: Missing expected AWF alias commands" + cat "$DOCKER_LOG" + exit 1 + fi + if grep -q 'alpine:3.17 alpine:latest' "$DOCKER_LOG"; then + echo -e "${RED}✗ FAIL${NC}: Unrelated image was incorrectly aliased to latest" + cat "$DOCKER_LOG" + exit 1 + else + echo -e "${GREEN}✓ PASS${NC}: Unrelated repositories were not aliased to latest" + fi +else + echo -e "${RED}✗ FAIL${NC}: Digest alias test failed" + cat /tmp/test3.log + exit 1 +fi +echo "" + +# Test 4: Already cached images (should be fast) +echo -e "${YELLOW}Test 4: Already cached images${NC}" START_TIME=$(date +%s) -if bash "$DOWNLOAD_SCRIPT" alpine:3.19 alpine:3.18 > /tmp/test3.log 2>&1; then +if bash "$DOWNLOAD_SCRIPT" alpine:3.19 alpine:3.18 > /tmp/test4.log 2>&1; then END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) echo -e "${GREEN}✓ PASS${NC}: Cached images download succeeded (${DURATION}s)" @@ -65,20 +121,20 @@ if bash "$DOWNLOAD_SCRIPT" alpine:3.19 alpine:3.18 > /tmp/test3.log 2>&1; then fi else echo -e "${RED}✗ FAIL${NC}: Cached images download failed" - cat /tmp/test3.log + cat /tmp/test4.log exit 1 fi echo "" -# Test 4: Invalid image (should fail gracefully) -echo -e "${YELLOW}Test 4: Invalid image (expected to fail)${NC}" -if bash "$DOWNLOAD_SCRIPT" "nonexistent-registry.invalid/fake-image:v999" > /tmp/test4.log 2>&1; then +# Test 5: Invalid image (should fail gracefully) +echo -e "${YELLOW}Test 5: Invalid image (expected to fail)${NC}" +if bash "$DOWNLOAD_SCRIPT" "nonexistent-registry.invalid/fake-image:v999" > /tmp/test5.log 2>&1; then echo -e "${RED}✗ FAIL${NC}: Should have failed for invalid image" exit 1 else echo -e "${GREEN}✓ PASS${NC}: Failed as expected for invalid image" # Check for expected error message - if grep -q "Failed to download" /tmp/test4.log; then + if grep -q "Failed to download" /tmp/test5.log; then echo -e "${GREEN}✓ PASS${NC}: Error message present" else echo -e "${YELLOW}⚠ WARNING${NC}: Expected error message format not found" @@ -86,9 +142,9 @@ else fi echo "" -# Test 5: Empty arguments (should handle gracefully) -echo -e "${YELLOW}Test 5: No images provided${NC}" -if bash "$DOWNLOAD_SCRIPT" > /tmp/test5.log 2>&1; then +# Test 6: Empty arguments (should handle gracefully) +echo -e "${YELLOW}Test 6: No images provided${NC}" +if bash "$DOWNLOAD_SCRIPT" > /tmp/test6.log 2>&1; then echo -e "${GREEN}✓ PASS${NC}: Handled empty arguments gracefully" else # This might fail which is also acceptable behavior @@ -102,3 +158,4 @@ echo "==========================================" # Cleanup rm -f /tmp/test*.log +cleanup_mock_docker From 105b9c3ee1b65db299c31cfb5482ae80860832d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:28:20 +0000 Subject: [PATCH 5/5] chore: plan latest follow-up Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/discussion-task-miner.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index 4f74ed95383..4e32bca860d 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a33ed75d7a3b3250217e65bb04247452c200e0e1a6011929441296ebe2f8b331","body_hash":"30a2a145f9a81af5f4df91cb34aaee48c6671db9721c0f6bd19dd73c859f0663","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.77","copilot-sdk":"1.0.8"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43","digest":"sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43","digest":"sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43","digest":"sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43","digest":"sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.7","digest":"sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _