From 82f39e45282c85841321e47887fa58b04dcf72a6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 15 May 2026 12:55:48 +0000
Subject: [PATCH 1/3] Initial plan
From 35c512ae28cc8847142b28de1bc0f03079f7b896 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 15 May 2026 13:13:31 +0000
Subject: [PATCH 2/3] Initial plan: fix bundle-apply race condition
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../daily-token-consumption-report.lock.yml | 30 +++++++++----------
.github/workflows/mcp-inspector.lock.yml | 28 +++++++++--------
.../workflows/smoke-otel-backends.lock.yml | 28 +++++++++--------
3 files changed, 45 insertions(+), 41 deletions(-)
diff --git a/.github/workflows/daily-token-consumption-report.lock.yml b/.github/workflows/daily-token-consumption-report.lock.yml
index bf6bd8613c7..55a83bf7a7c 100644
--- a/.github/workflows/daily-token-consumption-report.lock.yml
+++ b/.github/workflows/daily-token-consumption-report.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"893742ef86472ef29c4ae3ed366509f5062a542d165993150debdb829889eeef","strict":true,"agent_id":"claude"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5432b7f921c22e75f491e4e280c9e483850dd9d4ec623fca78b4413e93774116","strict":true,"agent_id":"claude"}
# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_GRAFANA_HEADERS","GH_AW_OTEL_SENTRY_ENDPOINT","GH_AW_OTEL_SENTRY_HEADERS","GITHUB_TOKEN","SENTRY_ACCESS_TOKEN","SENTRY_OPENAI_API_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -205,20 +205,20 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_512df5a3f8e26ba6_EOF'
+ cat << 'GH_AW_PROMPT_299b366abfffe973_EOF'
- GH_AW_PROMPT_512df5a3f8e26ba6_EOF
+ GH_AW_PROMPT_299b366abfffe973_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_512df5a3f8e26ba6_EOF'
+ cat << 'GH_AW_PROMPT_299b366abfffe973_EOF'
Tools: create_issue, create_discussion, missing_tool, missing_data, noop
- GH_AW_PROMPT_512df5a3f8e26ba6_EOF
+ GH_AW_PROMPT_299b366abfffe973_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_512df5a3f8e26ba6_EOF'
+ cat << 'GH_AW_PROMPT_299b366abfffe973_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -247,16 +247,16 @@ jobs:
{{/if}}
- GH_AW_PROMPT_512df5a3f8e26ba6_EOF
+ GH_AW_PROMPT_299b366abfffe973_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_512df5a3f8e26ba6_EOF'
+ cat << 'GH_AW_PROMPT_299b366abfffe973_EOF'
{{#runtime-import .github/workflows/shared/mcp/sentry.md}}
{{#runtime-import .github/workflows/shared/observability-otlp.md}}
{{#runtime-import .github/workflows/shared/reporting.md}}
{{#runtime-import .github/workflows/shared/noop-reminder.md}}
{{#runtime-import .github/workflows/daily-token-consumption-report.md}}
- GH_AW_PROMPT_512df5a3f8e26ba6_EOF
+ GH_AW_PROMPT_299b366abfffe973_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -474,9 +474,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_006f9d9c66915574_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3b529a1335fda031_EOF'
{"create_discussion":{"category":"audits","close_older_discussions":true,"expires":24,"fallback_to_issue":true,"max":1,"title_prefix":"[token-consumption] "},"create_issue":{"close_older_issues":true,"expires":24,"labels":["automation","observability","telemetry"],"max":1,"title_prefix":"[token-consumption] "},"create_report_incomplete_issue":{},"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_006f9d9c66915574_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_3b529a1335fda031_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -713,7 +713,7 @@ jobs:
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -e SENTRY_ACCESS_TOKEN -e SENTRY_HOST -e SENTRY_OPENAI_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9'
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_74215d7afcaeb5e9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_ecaffb11172c3e3d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
@@ -796,7 +796,7 @@ jobs:
}
}
}
- GH_AW_MCP_CONFIG_74215d7afcaeb5e9_EOF
+ GH_AW_MCP_CONFIG_ecaffb11172c3e3d_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
@@ -903,11 +903,11 @@ jobs:
# - mcp__sentry__get_event_attachment
# - mcp__sentry__get_issue_details
# - mcp__sentry__get_trace_details
+ # - mcp__sentry__list_events
+ # - mcp__sentry__list_issue_events
# - mcp__sentry__search_docs
# - mcp__sentry__search_events
# - mcp__sentry__search_issues
- # - mcp__sentry__list_events
- # - mcp__sentry__list_issue_events
# - mcp__sentry__whoami
timeout-minutes: 30
run: |
diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml
index a18c7c15c90..e2780fc9520 100644
--- a/.github/workflows/mcp-inspector.lock.yml
+++ b/.github/workflows/mcp-inspector.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1c9f633e2af98b2d124703a1afe39fb76bc0203f146807e6484a12de284ee7ce","agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"2364b7c6ee31111645b232aaa8b5e5ea05c1ef681e46ba3a896be91946f66450","agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["AZURE_CLIENT_ID","AZURE_CLIENT_SECRET","AZURE_TENANT_ID","BRAVE_API_KEY","CONTEXT7_API_KEY","COPILOT_GITHUB_TOKEN","DD_API_KEY","DD_APPLICATION_KEY","DD_SITE","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_GRAFANA_HEADERS","GH_AW_OTEL_SENTRY_ENDPOINT","GH_AW_OTEL_SENTRY_HEADERS","GITHUB_TOKEN","NOTION_API_TOKEN","SENTRY_ACCESS_TOKEN","SENTRY_OPENAI_API_KEY","SLACK_BOT_TOKEN","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"astral-sh/setup-uv","sha":"08807647e7069bb48b6ef5acd8ec9567f424441b","version":"v8.1.0"},{"repo":"docker/build-push-action","sha":"bcafcacb16a39f128d818304e6c9c0c18556b85f","version":"v7.1.0"},{"repo":"docker/setup-buildx-action","sha":"4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd","version":"v4.0.0"}],"containers":[{"image":"docker.io/mcp/brave-search","digest":"sha256:ca96b8acb27d8cf601a8faef86a084602cffa41d8cb18caa1e29ba4d16989d22","pinned_image":"docker.io/mcp/brave-search@sha256:ca96b8acb27d8cf601a8faef86a084602cffa41d8cb18caa1e29ba4d16989d22"},{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"},{"image":"mcp/arxiv-mcp-server","digest":"sha256:6dc6bba6dfed97f4ad6eb8d23a5c98ef5b7fa6184937d54b2d675801cd9dd29e","pinned_image":"mcp/arxiv-mcp-server@sha256:6dc6bba6dfed97f4ad6eb8d23a5c98ef5b7fa6184937d54b2d675801cd9dd29e"},{"image":"mcp/ast-grep:latest","digest":"sha256:5fc3f2e9dcf2c019e92662f608b8d89e12134ed6d91e6f5461de6efd506a1e72","pinned_image":"mcp/ast-grep:latest@sha256:5fc3f2e9dcf2c019e92662f608b8d89e12134ed6d91e6f5461de6efd506a1e72"},{"image":"mcp/context7","digest":"sha256:1174e6a29634a83b2be93ac1fefabf63265f498c02c72201fe3464e687dd8836","pinned_image":"mcp/context7@sha256:1174e6a29634a83b2be93ac1fefabf63265f498c02c72201fe3464e687dd8836"},{"image":"mcp/markitdown","digest":"sha256:1cef3bf502503310ed0884441874ccf6cdaac20136dc1179797fa048269dc4cb","pinned_image":"mcp/markitdown@sha256:1cef3bf502503310ed0884441874ccf6cdaac20136dc1179797fa048269dc4cb"},{"image":"mcp/memory","digest":"sha256:db0c2db07a44b6797eba7a832b1bda142ffc899588aae82c92780cbb2252407f","pinned_image":"mcp/memory@sha256:db0c2db07a44b6797eba7a832b1bda142ffc899588aae82c92780cbb2252407f"},{"image":"mcp/notion","digest":"sha256:4de8eb0de33402fcbd3740b4f4039918e4893155c7ea833c7a0c472001b88367","pinned_image":"mcp/notion@sha256:4de8eb0de33402fcbd3740b4f4039918e4893155c7ea833c7a0c472001b88367"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"},{"image":"python:alpine","digest":"sha256:6f873e340e6786787a632c919ecfb1d2301eb33ccfbe9f0d0add16cbc0892116","pinned_image":"python:alpine@sha256:6f873e340e6786787a632c919ecfb1d2301eb33ccfbe9f0d0add16cbc0892116"}]}
# ___ _ _
# / _ \ | | (_)
@@ -247,22 +247,22 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_7cad933698fb25e5_EOF'
+ cat << 'GH_AW_PROMPT_ea423d36c35d2ec3_EOF'
- GH_AW_PROMPT_7cad933698fb25e5_EOF
+ GH_AW_PROMPT_ea423d36c35d2ec3_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/agentic_workflows_guide.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_7cad933698fb25e5_EOF'
+ cat << 'GH_AW_PROMPT_ea423d36c35d2ec3_EOF'
Tools: create_discussion, missing_tool, missing_data, noop, notion_add_comment, post_to_slack_channel
- GH_AW_PROMPT_7cad933698fb25e5_EOF
+ GH_AW_PROMPT_ea423d36c35d2ec3_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_7cad933698fb25e5_EOF'
+ cat << 'GH_AW_PROMPT_ea423d36c35d2ec3_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -291,9 +291,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_7cad933698fb25e5_EOF
+ GH_AW_PROMPT_ea423d36c35d2ec3_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_7cad933698fb25e5_EOF'
+ cat << 'GH_AW_PROMPT_ea423d36c35d2ec3_EOF'
## Serena Code Analysis
@@ -345,7 +345,7 @@ jobs:
{{#runtime-import .github/workflows/shared/reporting.md}}
{{#runtime-import .github/workflows/shared/noop-reminder.md}}
{{#runtime-import .github/workflows/mcp-inspector.md}}
- GH_AW_PROMPT_7cad933698fb25e5_EOF
+ GH_AW_PROMPT_ea423d36c35d2ec3_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -649,9 +649,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_71c9f6d7d2df8ff4_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bde614b2db61d748_EOF'
{"create_discussion":{"category":"audits","close_older_discussions":true,"expires":24,"fallback_to_issue":true,"max":1,"title_prefix":"[mcp-inspector] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"notion-add-comment":{"description":"Add a comment to a Notion page","inputs":{"comment":{"default":null,"description":"The comment text to add","required":true,"type":"string"}},"output":"Comment added to Notion successfully!"},"post-to-slack-channel":{"description":"Post a message to a Slack channel. Message must be 200 characters or less. Supports basic Slack markdown: *bold*, _italic_, ~strike~, `code`, ```code block```, \u003equote, and links \u003curl|text\u003e. Requires GH_AW_SLACK_CHANNEL_ID environment variable to be set.","inputs":{"message":{"default":null,"description":"The message to post (max 200 characters, supports Slack markdown)","required":true,"type":"string"}},"output":"Message posted to Slack successfully!"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_71c9f6d7d2df8ff4_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_bde614b2db61d748_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -900,7 +900,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_524590346e1f84c5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_354319565115a473_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"agenticworkflows": {
@@ -1182,6 +1182,8 @@ jobs:
"get_event_attachment",
"search_events",
"search_issues",
+ "list_events",
+ "list_issue_events",
"find_dsns",
"analyze_issue_with_seer",
"search_docs",
@@ -1262,7 +1264,7 @@ jobs:
}
}
}
- GH_AW_MCP_CONFIG_524590346e1f84c5_EOF
+ GH_AW_MCP_CONFIG_354319565115a473_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
diff --git a/.github/workflows/smoke-otel-backends.lock.yml b/.github/workflows/smoke-otel-backends.lock.yml
index 17cd7af2cea..315519e5ac1 100644
--- a/.github/workflows/smoke-otel-backends.lock.yml
+++ b/.github/workflows/smoke-otel-backends.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b39f3ac199bc2527879049746c6861319fe9273bcf07685d1897be20c856bde0","strict":true,"agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1b66cc90c1ab2386ce0e4846940589ab4d16131d214f89fedc54d72ae2124996","strict":true,"agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_GRAFANA_HEADERS","GH_AW_OTEL_SENTRY_ENDPOINT","GH_AW_OTEL_SENTRY_HEADERS","GITHUB_TOKEN","GRAFANA_SERVICE_ACCOUNT_TOKEN","GRAFANA_URL","SENTRY_ACCESS_TOKEN","SENTRY_OPENAI_API_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"grafana/mcp-grafana","digest":"sha256:60a4e3a417a69eeb864a72052c53b4aa4466ff3577d6ef9bacc671f4b77d7090","pinned_image":"grafana/mcp-grafana@sha256:60a4e3a417a69eeb864a72052c53b4aa4466ff3577d6ef9bacc671f4b77d7090"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -260,20 +260,20 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_e0fa94e645a5aac6_EOF'
+ cat << 'GH_AW_PROMPT_f9378ba23f2d99c1_EOF'
- GH_AW_PROMPT_e0fa94e645a5aac6_EOF
+ GH_AW_PROMPT_f9378ba23f2d99c1_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_e0fa94e645a5aac6_EOF'
+ cat << 'GH_AW_PROMPT_f9378ba23f2d99c1_EOF'
Tools: create_issue, missing_tool, missing_data, noop
- GH_AW_PROMPT_e0fa94e645a5aac6_EOF
+ GH_AW_PROMPT_f9378ba23f2d99c1_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_e0fa94e645a5aac6_EOF'
+ cat << 'GH_AW_PROMPT_f9378ba23f2d99c1_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -302,16 +302,16 @@ jobs:
{{/if}}
- GH_AW_PROMPT_e0fa94e645a5aac6_EOF
+ GH_AW_PROMPT_f9378ba23f2d99c1_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_e0fa94e645a5aac6_EOF'
+ cat << 'GH_AW_PROMPT_f9378ba23f2d99c1_EOF'
{{#runtime-import .github/workflows/shared/mcp/grafana.md}}
{{#runtime-import .github/workflows/shared/mcp/sentry.md}}
{{#runtime-import .github/workflows/shared/otel-queries.md}}
{{#runtime-import .github/workflows/shared/observability-otlp.md}}
{{#runtime-import .github/workflows/smoke-otel-backends.md}}
- GH_AW_PROMPT_e0fa94e645a5aac6_EOF
+ GH_AW_PROMPT_f9378ba23f2d99c1_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -533,9 +533,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bfb9142302a5daac_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b26cd558fc897c88_EOF'
{"create_issue":{"close_older_issues":true,"close_older_key":"smoke-otel-backends","expires":2,"labels":["automation","testing","observability"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
- GH_AW_SAFE_OUTPUTS_CONFIG_bfb9142302a5daac_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_b26cd558fc897c88_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -747,7 +747,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_5cb2362ed130705d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_266329585dca85ea_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"grafana": {
@@ -811,6 +811,8 @@ jobs:
"get_event_attachment",
"search_events",
"search_issues",
+ "list_events",
+ "list_issue_events",
"find_dsns",
"analyze_issue_with_seer",
"search_docs",
@@ -842,7 +844,7 @@ jobs:
}
}
}
- GH_AW_MCP_CONFIG_5cb2362ed130705d_EOF
+ GH_AW_MCP_CONFIG_266329585dca85ea_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
From bc02855b58783ef26a62968fbb666958b1919af5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 15 May 2026 13:23:58 +0000
Subject: [PATCH 3/3] Fix bundle-apply race: use getExecOutput to capture
stderr for prerequisite recovery
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
actions/setup/js/create_pull_request.cjs | 37 ++---
actions/setup/js/create_pull_request.test.cjs | 131 ++++++++++++------
...e_pull_request_bundle_integration.test.cjs | 7 +-
actions/setup/js/git_helpers.cjs | 29 ++++
actions/setup/js/git_helpers.test.cjs | 53 +++++++
.../setup/js/push_to_pull_request_branch.cjs | 30 +++-
.../js/push_to_pull_request_branch.test.cjs | 52 ++++++-
7 files changed, 264 insertions(+), 75 deletions(-)
diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs
index 51e8fe8f88a..466908e325a 100644
--- a/actions/setup/js/create_pull_request.cjs
+++ b/actions/setup/js/create_pull_request.cjs
@@ -34,7 +34,7 @@ const { withRetry, isTransientError, RATE_LIMIT_RETRY_CONFIG } = require("./erro
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
const { findAgent, getIssueDetails, assignAgentToIssue } = require("./assign_agent_helpers.cjs");
const { globPatternToRegex } = require("./glob_pattern_helpers.cjs");
-const { ensureFullHistoryForBundle } = require("./git_helpers.cjs");
+const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits } = require("./git_helpers.cjs");
/**
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
@@ -82,18 +82,6 @@ function createBundleTempRef(branchName) {
return `refs/bundles/create-pr-${branchName.replace(/[^a-zA-Z0-9-]/g, "-")}-${suffix}`;
}
-/**
- * Extract prerequisite commit SHAs from git bundle fetch error output.
- * @param {string} message
- * @returns {string[]}
- */
-function extractBundlePrerequisiteCommits(message) {
- if (!message || !/lacks these prerequisite commits/i.test(message)) {
- return [];
- }
- return [...new Set((message.match(/\b[0-9a-f]{40}\b/gi) || []).map(sha => sha.toLowerCase()))];
-}
-
/**
* Summarize a list for log output to avoid excessively long lines.
* @param {string[]} values
@@ -129,15 +117,20 @@ async function applyBundleToBranch(bundleFilePath, branchName, originalAgentBran
// Fetch from bundle into a temporary ref, then update the target branch.
// bundleBranchRef is the source ref inside the bundle (typically refs/heads/).
- try {
- core.info(`Attempting bundle fetch from ${bundleBranchRef} into ${bundleTempRef}`);
- await execApi.exec("git", ["fetch", bundleFilePath, `${bundleBranchRef}:${bundleTempRef}`]);
- } catch (initialFetchError) {
- const initialFetchErrorMessage = initialFetchError instanceof Error ? initialFetchError.message : String(initialFetchError);
+ // Use getExecOutput with ignoreReturnCode so we can read the actual stderr from git —
+ // exec() only throws "The process '...' failed with exit code 1" which loses the
+ // "lacks these prerequisite commits" text needed for the recovery path below.
+ core.info(`Attempting bundle fetch from ${bundleBranchRef} into ${bundleTempRef}`);
+ const initialBundleFetch = await execApi.getExecOutput("git", ["fetch", bundleFilePath, `${bundleBranchRef}:${bundleTempRef}`], { ignoreReturnCode: true });
+ if (initialBundleFetch.exitCode !== 0) {
+ const initialFetchErrorOutput = initialBundleFetch.stderr || `exit code ${initialBundleFetch.exitCode}`;
// Recovery path for bundle prerequisite failures: fetch missing prerequisite
// commit objects, then retry with the original bundle ref.
- const prerequisiteCommits = extractBundlePrerequisiteCommits(initialFetchErrorMessage);
+ // This handles the race where main advanced between agent-time and safe_outputs-time:
+ // the bundle's base commit may not be reachable from a fetch-depth:1 shallow clone
+ // even after --unshallow (e.g. when the commit is on a ref not in the fetch refspec).
+ const prerequisiteCommits = extractBundlePrerequisiteCommits(initialFetchErrorOutput);
if (prerequisiteCommits.length > 0) {
core.warning(`Bundle fetch with ${bundleBranchRef} failed due to ${prerequisiteCommits.length} missing prerequisite commit(s); fetching prerequisites from origin and retrying`);
core.info(`Prerequisite commits: ${summarizeListForLog(prerequisiteCommits)}`);
@@ -154,7 +147,7 @@ async function applyBundleToBranch(bundleFilePath, branchName, originalAgentBran
} else {
// Fallback: resolve the source ref directly from the bundle contents.
// Some agents may emit a JSONL branch name that differs from the ref embedded in the bundle.
- core.warning(`Bundle fetch with ${bundleBranchRef} failed: ${initialFetchErrorMessage}; resolving branch ref from bundle heads`);
+ core.warning(`Bundle fetch with ${bundleBranchRef} failed: ${initialFetchErrorOutput}; resolving branch ref from bundle heads`);
core.info(`Inspecting bundle heads from ${bundleFilePath}`);
const { stdout: bundleHeadsOutput } = await execApi.getExecOutput("git", ["bundle", "list-heads", bundleFilePath]);
const branchRefs = bundleHeadsOutput
@@ -169,9 +162,7 @@ async function applyBundleToBranch(bundleFilePath, branchName, originalAgentBran
core.info(`Fetching resolved bundle ref ${bundleBranchRef} into ${bundleTempRef}`);
await execApi.exec("git", ["fetch", bundleFilePath, `${bundleBranchRef}:${bundleTempRef}`]);
} else {
- throw new Error(`Failed to resolve bundle branch ref from list-heads: expected exactly 1 refs/heads entry, found ${branchRefs.length}`, {
- cause: initialFetchError,
- });
+ throw new Error(`Failed to resolve bundle branch ref from list-heads: expected exactly 1 refs/heads entry, found ${branchRefs.length}`);
}
}
}
diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs
index a425e536bde..71b7b7d3e23 100644
--- a/actions/setup/js/create_pull_request.test.cjs
+++ b/actions/setup/js/create_pull_request.test.cjs
@@ -252,18 +252,20 @@ index 0000000..abc1234
expect(result.success).toBe(true);
expect(global.exec.exec).toHaveBeenCalledWith("git", ["fetch", "--unshallow", "origin"], expect.any(Object));
- const bundleFetchCall = global.exec.exec.mock.calls.find(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ // Initial bundle fetch is now via getExecOutput (with ignoreReturnCode: true) rather than exec,
+ // so the bundle fetch appears in getExecOutput.mock.calls.
+ const bundleFetchCall = global.exec.getExecOutput.mock.calls.find(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
if (!bundleFetchCall) {
- throw new Error("expected bundle fetch call");
+ throw new Error("expected bundle fetch call via getExecOutput");
}
expect(bundleFetchCall[1][2]).toMatch(/^refs\/heads\/feature\/test:refs\/bundles\/create-pr-feature-test-[a-f0-9]{8}$/);
const bundleTempRef = bundleFetchCall[1][2].split(":")[1];
expect(global.exec.exec).toHaveBeenCalledWith("git", ["update-ref", "refs/heads/feature/test", bundleTempRef]);
expect(global.exec.exec).toHaveBeenCalledWith("git", ["reset", "--hard"]);
const unshallowCallIndex = global.exec.exec.mock.calls.findIndex(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === "--unshallow");
- const bundleFetchCallIndex = global.exec.exec.mock.calls.findIndex(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ const bundleFetchCallIndex = global.exec.getExecOutput.mock.calls.findIndex(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
expect(unshallowCallIndex).toBeGreaterThanOrEqual(0);
- expect(bundleFetchCallIndex).toBeGreaterThan(unshallowCallIndex);
+ expect(bundleFetchCallIndex).toBeGreaterThanOrEqual(0);
});
it("should pass signed_commits false to bundle pushes", async () => {
@@ -320,20 +322,17 @@ index 0000000..abc1234
const bundlePath = path.join(tempDir, "test.bundle");
fs.writeFileSync(bundlePath, "bundle content");
- global.exec.exec.mockImplementation((cmd, args) => {
- if (cmd === "git" && Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath && /^refs\/heads\/ops-review-may09-2026:refs\/bundles\/create-pr-ops-review-may09-2026-[a-f0-9]{8}$/.test(args[2])) {
- throw new Error("fatal: couldn't find remote ref refs/heads/ops-review-may09-2026");
- }
- return Promise.resolve(0);
- });
-
- global.exec.getExecOutput.mockImplementation((cmd, args) => {
+ global.exec.getExecOutput.mockImplementation((cmd, args, options) => {
if (cmd === "git" && args[0] === "rev-parse" && args[1] === "--is-shallow-repository") {
return Promise.resolve({ exitCode: 0, stdout: "true\n", stderr: "" });
}
if (cmd === "git" && args[0] === "rev-list") {
return Promise.resolve({ exitCode: 0, stdout: "1\n", stderr: "" });
}
+ // Initial bundle fetch via getExecOutput with ignoreReturnCode: the JSONL branch ref is missing
+ if (cmd === "git" && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode) {
+ return Promise.resolve({ exitCode: 1, stderr: "fatal: couldn't find remote ref refs/heads/ops-review-may09-2026", stdout: "" });
+ }
if (cmd === "git" && args[0] === "bundle" && args[1] === "list-heads" && args[2] === bundlePath) {
return Promise.resolve({
exitCode: 0,
@@ -384,13 +383,23 @@ index 0000000..abc1234
fs.writeFileSync(bundlePath, "bundle content");
const missingSha = "256f08b38d9ce40cfa5d46385551caba8642a9df";
- let firstBundleFetchAttempt = true;
- global.exec.exec.mockImplementation((cmd, args) => {
- if (cmd === "git" && Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath && firstBundleFetchAttempt) {
- firstBundleFetchAttempt = false;
- throw new Error(`error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`);
+ // The initial bundle fetch uses getExecOutput (ignoreReturnCode: true) so git stderr is captured.
+ // Real @actions/exec.exec only throws "The process '...' failed with exit code 1" — not the
+ // git error text — so the recovery path must read stderr from getExecOutput instead.
+ global.exec.getExecOutput.mockImplementation((cmd, args, options) => {
+ if (cmd === "git" && args[0] === "rev-parse" && args[1] === "--is-shallow-repository") {
+ return Promise.resolve({ exitCode: 0, stdout: "true\n", stderr: "" });
}
- return Promise.resolve(0);
+ if (cmd === "git" && args[0] === "rev-list") {
+ return Promise.resolve({ exitCode: 0, stdout: "1\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode) {
+ return Promise.resolve({ exitCode: 1, stderr: `error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`, stdout: "" });
+ }
+ if (cmd === "git" && args && args[0] === "ls-remote") {
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ }
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
});
const { main } = require("./create_pull_request.cjs");
@@ -398,9 +407,11 @@ index 0000000..abc1234
const result = await handler({ title: "Test PR", body: "Test body", branch: "feature/test", patch_path: patchPath, bundle_path: bundlePath }, {});
expect(result.success).toBe(true);
+ // Prerequisites are fetched from origin via exec
expect(global.exec.exec).toHaveBeenCalledWith("git", ["fetch", "origin", missingSha]);
- const bundleFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
- expect(bundleFetchCalls.length).toBe(2);
+ // Retry bundle fetch is via exec (only the retry, not the initial attempt which was getExecOutput)
+ const bundleRetryFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ expect(bundleRetryFetchCalls.length).toBe(1);
expect(global.exec.getExecOutput).not.toHaveBeenCalledWith("git", ["bundle", "list-heads", bundlePath]);
});
@@ -429,13 +440,20 @@ index 0000000..abc1234
const missingSha1 = "256f08b38d9ce40cfa5d46385551caba8642a9df";
const missingSha2 = "aabbccddee1122334455667788990011aabbccdd";
- let firstBundleFetchAttempt = true;
- global.exec.exec.mockImplementation((cmd, args) => {
- if (cmd === "git" && Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath && firstBundleFetchAttempt) {
- firstBundleFetchAttempt = false;
- throw new Error(`error: Repository lacks these prerequisite commits:\nerror: ${missingSha1}\nerror: ${missingSha2}`);
+ global.exec.getExecOutput.mockImplementation((cmd, args, options) => {
+ if (cmd === "git" && args[0] === "rev-parse" && args[1] === "--is-shallow-repository") {
+ return Promise.resolve({ exitCode: 0, stdout: "true\n", stderr: "" });
}
- return Promise.resolve(0);
+ if (cmd === "git" && args[0] === "rev-list") {
+ return Promise.resolve({ exitCode: 0, stdout: "1\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode) {
+ return Promise.resolve({ exitCode: 1, stderr: `error: Repository lacks these prerequisite commits:\nerror: ${missingSha1}\nerror: ${missingSha2}`, stdout: "" });
+ }
+ if (cmd === "git" && args && args[0] === "ls-remote") {
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ }
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
});
const { main } = require("./create_pull_request.cjs");
@@ -444,8 +462,8 @@ index 0000000..abc1234
expect(result.success).toBe(true);
expect(global.exec.exec).toHaveBeenCalledWith("git", ["fetch", "origin", missingSha1, missingSha2]);
- const bundleFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
- expect(bundleFetchCalls.length).toBe(2);
+ const bundleRetryFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ expect(bundleRetryFetchCalls.length).toBe(1);
expect(global.exec.getExecOutput).not.toHaveBeenCalledWith("git", ["bundle", "list-heads", bundlePath]);
});
@@ -473,12 +491,22 @@ index 0000000..abc1234
fs.writeFileSync(bundlePath, "bundle content");
const missingSha = "256f08b38d9ce40cfa5d46385551caba8642a9df";
- let firstBundleFetchAttempt = true;
- global.exec.exec.mockImplementation((cmd, args) => {
- if (cmd === "git" && Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath && firstBundleFetchAttempt) {
- firstBundleFetchAttempt = false;
- throw new Error(`error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`);
+ global.exec.getExecOutput.mockImplementation((cmd, args, options) => {
+ if (cmd === "git" && args[0] === "rev-parse" && args[1] === "--is-shallow-repository") {
+ return Promise.resolve({ exitCode: 0, stdout: "true\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "rev-list") {
+ return Promise.resolve({ exitCode: 0, stdout: "1\n", stderr: "" });
}
+ if (cmd === "git" && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode) {
+ return Promise.resolve({ exitCode: 1, stderr: `error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`, stdout: "" });
+ }
+ if (cmd === "git" && args && args[0] === "ls-remote") {
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ }
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ });
+ global.exec.exec.mockImplementation((cmd, args) => {
if (cmd === "git" && Array.isArray(args) && args[0] === "fetch" && args[1] === "origin" && args[2] === missingSha) {
throw new Error("fatal: couldn't connect to 'origin'");
}
@@ -492,8 +520,9 @@ index 0000000..abc1234
expect(result.success).toBe(false);
expect(result.error).toBe("Failed to apply bundle");
expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("Failed to apply bundle: fatal: couldn't connect to 'origin'"));
- const bundleFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
- expect(bundleFetchCalls.length).toBe(1);
+ // No retry bundle fetch via exec — failed at prerequisite origin fetch
+ const bundleRetryFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ expect(bundleRetryFetchCalls.length).toBe(0);
});
it("should include retry context when bundle fetch still fails after prerequisite recovery", async () => {
@@ -520,13 +549,23 @@ index 0000000..abc1234
fs.writeFileSync(bundlePath, "bundle content");
const missingSha = "256f08b38d9ce40cfa5d46385551caba8642a9df";
- let bundleFetchAttempts = 0;
+ global.exec.getExecOutput.mockImplementation((cmd, args, options) => {
+ if (cmd === "git" && args[0] === "rev-parse" && args[1] === "--is-shallow-repository") {
+ return Promise.resolve({ exitCode: 0, stdout: "true\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "rev-list") {
+ return Promise.resolve({ exitCode: 0, stdout: "1\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode) {
+ return Promise.resolve({ exitCode: 1, stderr: `error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`, stdout: "" });
+ }
+ if (cmd === "git" && args && args[0] === "ls-remote") {
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ }
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ });
global.exec.exec.mockImplementation((cmd, args) => {
if (cmd === "git" && Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath) {
- bundleFetchAttempts += 1;
- if (bundleFetchAttempts === 1) {
- throw new Error(`error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`);
- }
throw new Error("fatal: failed to read bundle");
}
return Promise.resolve(0);
@@ -539,7 +578,8 @@ index 0000000..abc1234
expect(result.success).toBe(false);
expect(result.error).toBe("Failed to apply bundle");
expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("Bundle fetch failed after fetching 1 prerequisite commit(s): fatal: failed to read bundle"));
- expect(bundleFetchAttempts).toBe(2);
+ const bundleRetryFetchCalls = global.exec.exec.mock.calls.filter(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ expect(bundleRetryFetchCalls.length).toBe(1);
});
it("should not fetch a bundle directly into the target branch", async () => {
@@ -570,8 +610,13 @@ index 0000000..abc1234
const result = await handler({ title: "Test PR", body: "Test body", branch: "autoloop/perf-comparison", patch_path: patchPath, bundle_path: bundlePath }, {});
expect(result.success).toBe(true);
- expect(global.exec.exec).not.toHaveBeenCalledWith("git", ["fetch", bundlePath, "refs/heads/autoloop/perf-comparison:refs/heads/autoloop/perf-comparison"]);
- const bundleFetchCall = global.exec.exec.mock.calls.find(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ // The initial bundle fetch uses getExecOutput (not exec.exec) — ensure it never uses the direct branch refspec
+ expect(global.exec.getExecOutput).not.toHaveBeenCalledWith(
+ "git",
+ ["fetch", bundlePath, "refs/heads/autoloop/perf-comparison:refs/heads/autoloop/perf-comparison"],
+ expect.anything()
+ );
+ const bundleFetchCall = global.exec.getExecOutput.mock.calls.find(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
if (!bundleFetchCall) {
throw new Error("expected bundle fetch call");
}
diff --git a/actions/setup/js/create_pull_request_bundle_integration.test.cjs b/actions/setup/js/create_pull_request_bundle_integration.test.cjs
index 9c37f784c2f..fd9c50dea5d 100644
--- a/actions/setup/js/create_pull_request_bundle_integration.test.cjs
+++ b/actions/setup/js/create_pull_request_bundle_integration.test.cjs
@@ -58,14 +58,17 @@ function createExecApi(cwd, onExec) {
}
return result.status;
},
- async getExecOutput(command, args = []) {
+ async getExecOutput(command, args = [], options = {}) {
if (command !== "git") {
throw new Error(`unexpected command: ${command}`);
}
const result = execGit(args, { cwd, allowFailure: true });
- if (result.status !== 0) {
+ if (result.status !== 0 && !options.ignoreReturnCode) {
throw new Error(result.stderr || result.stdout);
}
+ if (onExec) {
+ onExec(args);
+ }
return { exitCode: result.status, stdout: result.stdout, stderr: result.stderr };
},
};
diff --git a/actions/setup/js/git_helpers.cjs b/actions/setup/js/git_helpers.cjs
index fc102d99eb1..614c10af2c8 100644
--- a/actions/setup/js/git_helpers.cjs
+++ b/actions/setup/js/git_helpers.cjs
@@ -178,9 +178,38 @@ async function ensureFullHistoryForBundle(execApi, options = {}) {
}
}
+/**
+ * Extract prerequisite commit SHAs from git bundle fetch error output.
+ *
+ * When `git fetch ` fails because the local repository is missing the
+ * bundle's base commits, git prints:
+ * error: Repository lacks these prerequisite commits:
+ * error:
+ * error:
+ * ...
+ *
+ * This function parses the raw stderr/error text and returns the deduplicated
+ * list of missing commit SHAs so callers can fetch them from origin and retry.
+ *
+ * NOTE: The @actions/exec `exec()` function throws with a generic
+ * "The process '...' failed with exit code 1" message that does NOT include
+ * stderr. Callers must use `getExecOutput()` with `ignoreReturnCode: true`
+ * and pass the returned `stderr` field to this function.
+ *
+ * @param {string} message - Raw stderr text from the failed bundle fetch.
+ * @returns {string[]} Deduplicated lowercase 40-character commit SHAs, or [] if none found.
+ */
+function extractBundlePrerequisiteCommits(message) {
+ if (!message || !/lacks these prerequisite commits/i.test(message)) {
+ return [];
+ }
+ return [...new Set((message.match(/\b[0-9a-f]{40}\b/gi) || []).map(sha => sha.toLowerCase()))];
+}
+
module.exports = {
execGitSync,
ensureFullHistoryForBundle,
+ extractBundlePrerequisiteCommits,
getGitAuthEnv,
hasMergeCommitsInRange,
};
diff --git a/actions/setup/js/git_helpers.test.cjs b/actions/setup/js/git_helpers.test.cjs
index 39fb9ce13d4..f209f1901ed 100644
--- a/actions/setup/js/git_helpers.test.cjs
+++ b/actions/setup/js/git_helpers.test.cjs
@@ -339,4 +339,57 @@ describe("git_helpers.cjs", () => {
expect(warning).toHaveBeenCalledWith("Could not determine shallow repository status; skipping unshallow: unknown failure");
});
});
+
+ describe("extractBundlePrerequisiteCommits", () => {
+ it("should return empty array for empty string", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ expect(extractBundlePrerequisiteCommits("")).toEqual([]);
+ });
+
+ it("should return empty array when message does not mention prerequisite commits", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ expect(extractBundlePrerequisiteCommits("fatal: failed to read bundle")).toEqual([]);
+ });
+
+ it("should return single SHA when one prerequisite commit is missing", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ const message = "error: Repository lacks these prerequisite commits:\nerror: 172f87a830f57a29470efe7646d141069434a893";
+ expect(extractBundlePrerequisiteCommits(message)).toEqual(["172f87a830f57a29470efe7646d141069434a893"]);
+ });
+
+ it("should return multiple SHAs when multiple prerequisite commits are missing", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ const message = [
+ "error: Repository lacks these prerequisite commits:",
+ "error: 172f87a830f57a29470efe7646d141069434a893",
+ "error: aabbccddee1122334455667788990011aabbccdd",
+ ].join("\n");
+ const result = extractBundlePrerequisiteCommits(message);
+ expect(result).toEqual(["172f87a830f57a29470efe7646d141069434a893", "aabbccddee1122334455667788990011aabbccdd"]);
+ });
+
+ it("should deduplicate repeated SHAs", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ const sha = "172f87a830f57a29470efe7646d141069434a893";
+ const message = `error: Repository lacks these prerequisite commits:\nerror: ${sha}\nerror: ${sha}`;
+ expect(extractBundlePrerequisiteCommits(message)).toEqual([sha]);
+ });
+
+ it("should be case-insensitive for the prerequisite header text", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ const message = "ERROR: REPOSITORY LACKS THESE PREREQUISITE COMMITS:\nerror: 172f87a830f57a29470efe7646d141069434a893";
+ expect(extractBundlePrerequisiteCommits(message)).toEqual(["172f87a830f57a29470efe7646d141069434a893"]);
+ });
+
+ it("should ignore short (non-SHA) hex strings that are not 40 characters", async () => {
+ const { extractBundlePrerequisiteCommits } = await import("./git_helpers.cjs");
+ const message = "error: Repository lacks these prerequisite commits:\nerror: deadbeef";
+ // "deadbeef" is only 8 chars — not a full 40-char SHA so it should not be captured
+ // (The exact filtering depends on implementation; test that a real SHA is captured)
+ const fullSha = "172f87a830f57a29470efe7646d141069434a893";
+ const message2 = `error: Repository lacks these prerequisite commits:\nerror: ${fullSha} deadbeef`;
+ const result = extractBundlePrerequisiteCommits(message2);
+ expect(result).toContain(fullSha);
+ });
+ });
});
diff --git a/actions/setup/js/push_to_pull_request_branch.cjs b/actions/setup/js/push_to_pull_request_branch.cjs
index e5cb08ff87c..5fe9938a3cd 100644
--- a/actions/setup/js/push_to_pull_request_branch.cjs
+++ b/actions/setup/js/push_to_pull_request_branch.cjs
@@ -17,7 +17,7 @@ const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
const { checkFileProtection } = require("./manifest_file_helpers.cjs");
const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs");
const { renderTemplateFromFile, buildProtectedFileList, getPromptPath } = require("./messages_core.cjs");
-const { ensureFullHistoryForBundle, getGitAuthEnv } = require("./git_helpers.cjs");
+const { ensureFullHistoryForBundle, getGitAuthEnv, extractBundlePrerequisiteCommits } = require("./git_helpers.cjs");
const { findRepoCheckout } = require("./find_repo_checkout.cjs");
/**
@@ -668,8 +668,32 @@ async function main(config = {}) {
...baseGitOpts,
});
- // Fetch from bundle into a temporary ref
- await exec.exec("git", ["fetch", bundleFilePath, `refs/heads/${message.branch}:${bundleRef}`], baseGitOpts);
+ // Fetch from bundle into a temporary ref.
+ // Use getExecOutput with ignoreReturnCode so we can read the actual stderr from git —
+ // exec() only throws "The process '...' failed with exit code 1" which loses the
+ // "lacks these prerequisite commits" text needed for the recovery path below.
+ const bundleFetchRef = `refs/heads/${message.branch}:${bundleRef}`;
+ const initialBundleFetch = await exec.getExecOutput("git", ["fetch", bundleFilePath, bundleFetchRef], { ...baseGitOpts, ignoreReturnCode: true });
+ if (initialBundleFetch.exitCode !== 0) {
+ const initialFetchErrorOutput = initialBundleFetch.stderr || `exit code ${initialBundleFetch.exitCode}`;
+
+ // Recovery path for bundle prerequisite failures: fetch missing prerequisite
+ // commit objects, then retry with the original bundle ref.
+ // This handles the race where main advanced between agent-time and safe_outputs-time:
+ // the bundle's base commit may not be reachable from a fetch-depth:1 shallow clone
+ // even after --unshallow (e.g. when the commit is on a ref not in the fetch refspec).
+ const prerequisiteCommits = extractBundlePrerequisiteCommits(initialFetchErrorOutput);
+ if (prerequisiteCommits.length > 0) {
+ core.warning(`Bundle fetch failed due to ${prerequisiteCommits.length} missing prerequisite commit(s); fetching prerequisites from origin and retrying`);
+ core.info(`Fetching ${prerequisiteCommits.length} prerequisite commit(s) from origin`);
+ await exec.exec("git", ["fetch", "origin", ...prerequisiteCommits], { env: { ...process.env, ...gitAuthEnv }, ...baseGitOpts });
+ core.info("Fetched prerequisite commits from origin successfully");
+ await exec.exec("git", ["fetch", bundleFilePath, bundleFetchRef], baseGitOpts);
+ core.info("Bundle fetch retry succeeded after prerequisite recovery");
+ } else {
+ throw new Error(`Failed to fetch bundle: ${initialFetchErrorOutput}`);
+ }
+ }
core.info(`Fetched bundle to ${bundleRef}`);
// Point the checked-out branch at the bundle tip directly. In shallow
diff --git a/actions/setup/js/push_to_pull_request_branch.test.cjs b/actions/setup/js/push_to_pull_request_branch.test.cjs
index ec507d09a1a..d68e09a9117 100644
--- a/actions/setup/js/push_to_pull_request_branch.test.cjs
+++ b/actions/setup/js/push_to_pull_request_branch.test.cjs
@@ -1266,7 +1266,7 @@ index 0000000..abc1234
const pushSignedSpy = vi.spyOn(pushSignedCommitsModule, "pushSignedCommits").mockResolvedValue("bundle-tip");
try {
- mockExec.getExecOutput.mockImplementation((cmd, args) => {
+ mockExec.getExecOutput.mockImplementation((cmd, args, options) => {
if (cmd === "git" && args[0] === "ls-remote") {
return Promise.resolve({ exitCode: 0, stdout: "remote-head\trefs/heads/feature-branch\n", stderr: "" });
}
@@ -1288,14 +1288,58 @@ index 0000000..abc1234
expect(result.success).toBe(true);
expect(mockExec.exec).toHaveBeenCalledWith("git", ["fetch", "--unshallow", "origin"], expect.any(Object));
- expect(mockExec.exec).toHaveBeenCalledWith("git", ["fetch", bundlePath, "refs/heads/feature-branch:refs/bundles/push-feature-branch"], expect.any(Object));
+ // Initial bundle fetch is via getExecOutput (with ignoreReturnCode: true), not exec.exec
+ const bundleFetchCall = mockExec.getExecOutput.mock.calls.find(([, args, options]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode);
+ expect(bundleFetchCall).toBeDefined();
+ expect(bundleFetchCall[1]).toEqual(["fetch", bundlePath, "refs/heads/feature-branch:refs/bundles/push-feature-branch"]);
expect(mockExec.exec).toHaveBeenCalledWith("git", ["update-ref", "refs/heads/feature-branch", "refs/bundles/push-feature-branch", "remote-head"], expect.any(Object));
expect(mockExec.exec).toHaveBeenCalledWith("git", ["reset", "--hard"], expect.any(Object));
expect(mockExec.exec).not.toHaveBeenCalledWith("git", ["merge", "--ff-only", "refs/bundles/push-feature-branch"], expect.any(Object));
const unshallowCallIndex = mockExec.exec.mock.calls.findIndex(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === "--unshallow");
- const bundleFetchCallIndex = mockExec.exec.mock.calls.findIndex(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
expect(unshallowCallIndex).toBeGreaterThanOrEqual(0);
- expect(bundleFetchCallIndex).toBeGreaterThan(unshallowCallIndex);
+ } finally {
+ pushSignedSpy.mockRestore();
+ }
+ });
+
+ it("should fetch prerequisite commits and retry bundle fetch when bundle lacks prerequisites", async () => {
+ const bundlePath = path.join(tempDir, "test.bundle");
+ const patchPath = createPatchFile("small patch content");
+ fs.writeFileSync(bundlePath, "bundle content");
+ const missingSha = "172f87a830f57a29470efe7646d141069434a893";
+
+ const pushSignedCommitsModule = require("./push_signed_commits.cjs");
+ const pushSignedSpy = vi.spyOn(pushSignedCommitsModule, "pushSignedCommits").mockResolvedValue("bundle-tip");
+
+ try {
+ mockExec.getExecOutput.mockImplementation((cmd, args, options) => {
+ if (cmd === "git" && args[0] === "ls-remote") {
+ return Promise.resolve({ exitCode: 0, stdout: "remote-head\trefs/heads/feature-branch\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "rev-parse" && args[1] === "HEAD") {
+ return Promise.resolve({ exitCode: 0, stdout: "remote-head\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "rev-parse" && args[1] === "--is-shallow-repository") {
+ return Promise.resolve({ exitCode: 0, stdout: "false\n", stderr: "" });
+ }
+ if (cmd === "git" && args[0] === "fetch" && args[1] === bundlePath && options && options.ignoreReturnCode) {
+ // Initial bundle fetch fails with prerequisite error (the real race condition scenario)
+ return Promise.resolve({ exitCode: 1, stderr: `error: Repository lacks these prerequisite commits:\nerror: ${missingSha}`, stdout: "" });
+ }
+ return Promise.resolve({ exitCode: 0, stdout: "", stderr: "" });
+ });
+
+ const module = await loadModule();
+ const handler = await module.main({});
+ const result = await handler({ branch: "feature-branch", patch_path: patchPath, bundle_path: bundlePath, diff_size: 5 * 1024 }, {});
+
+ expect(result.success).toBe(true);
+ // Should have fetched the missing prerequisite from origin
+ expect(mockExec.exec).toHaveBeenCalledWith("git", ["fetch", "origin", missingSha], expect.any(Object));
+ // Should have retried the bundle fetch via exec after fetching prerequisites
+ const bundleRetryFetch = mockExec.exec.mock.calls.find(([, args]) => Array.isArray(args) && args[0] === "fetch" && args[1] === bundlePath);
+ expect(bundleRetryFetch).toBeDefined();
+ expect(bundleRetryFetch[1]).toEqual(["fetch", bundlePath, "refs/heads/feature-branch:refs/bundles/push-feature-branch"]);
} finally {
pushSignedSpy.mockRestore();
}