diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 3ace36363bc..e0660b08e36 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7126ed9768737822881bf5a8530f13e7eb276788a5b053d9d038c214905bc50e","body_hash":"f3f7c30eedd5c2709fc93cda56721f423fea8d76201e4ca6a0b6358da4619769","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65","copilot-sdk":"1.0.4"}} -# 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":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"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"},{"repo":"github/gh-aw-actions/setup","sha":"v0.81.6","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} -# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7126ed9768737822881bf5a8530f13e7eb276788a5b053d9d038c214905bc50e","body_hash":"f3f7c30eedd5c2709fc93cda56721f423fea8d76201e4ca6a0b6358da4619769","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.67","copilot-sdk":"1.0.4"}} +# 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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"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.27.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.20"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.32","digest":"sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} +# 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 # # ___ _ _ # / _ \ | | (_) @@ -40,7 +40,6 @@ # - GITHUB_TOKEN # # Custom actions used: -# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 # - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 # - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -48,16 +47,15 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d -# - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d -# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-firewall/agent:0.27.20 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.20 +# - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.20 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.20 +# - ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 +# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 name: "Plan Command" on: @@ -120,9 +118,17 @@ jobs: text: ${{ steps.sanitized.outputs.text }} title: ${{ steps.sanitized.outputs.title }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -132,8 +138,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Mask OTLP telemetry headers run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" @@ -143,16 +149,15 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AGENT_VERSION: "1.0.65" - GH_AW_INFO_CLI_VERSION: "v0.81.6" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AGENT_VERSION: "1.0.67" GH_AW_INFO_WORKFLOW_NAME: "Plan Command" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["*.grafana.net","*.sentry.io","defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_INFO_FRONTMATTER_EMOJI: "📋" @@ -168,7 +173,7 @@ jobs: id: restore-daily-aic-cache if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} continue-on-error: true - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: key: agentic-workflow-usage-plan-${{ github.run_id }} restore-keys: agentic-workflow-usage-plan- @@ -233,6 +238,7 @@ jobs: sparse-checkout: | .github .agents + actions/setup .antigravity .claude .codex @@ -260,16 +266,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMPILED_VERSION: "v0.81.6" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); - await main(); - name: Compute current body text id: sanitized uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -525,6 +521,7 @@ jobs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} + http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} @@ -536,9 +533,17 @@ jobs: setup-trace-id: ${{ steps.setup.outputs.trace-id }} unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -547,8 +552,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -591,11 +596,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.67 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.20 --rootless - name: Install GitHub Copilot SDK (Node.js) run: cd "${GITHUB_WORKSPACE}" && npm install --ignore-scripts --no-save @github/copilot-sdk@1.0.4 - name: Parse integrity filter lists @@ -626,7 +631,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11 ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.20 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.20 ghcr.io/github/gh-aw-firewall/squid:0.27.20 ghcr.io/github/gh-aw-mcpg:v0.3.32@sha256:63e46b56dfd70895a701b6fc6dd0189e11e2d875f327f1781e81b31848735477 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -826,7 +831,7 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --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_POLICY_ALLOW_CREATE_PULL_REQUEST -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 RUNNER_TEMP -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -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 -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --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_POLICY_ALLOW_CREATE_PULL_REQUEST -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 RUNNER_TEMP -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -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 -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.32' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) @@ -903,9 +908,15 @@ jobs: env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_HOST: ${{ env.GH_HOST }} + GITHUB_HOST: ${{ env.GITHUB_HOST }} + GITHUB_ENTERPRISE_HOST: ${{ env.GITHUB_ENTERPRISE_HOST }} + GITHUB_GRAPHQL_URL: ${{ env.GITHUB_GRAPHQL_URL }} + GITHUB_COPILOT_BASE_URL: ${{ env.GITHUB_COPILOT_BASE_URL }} GH_AW_NETWORK_ISOLATION: 'true' CLI_PROXY_POLICY: '{"allow-only":{"min-integrity":"none","repos":"all"}}' - CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.3.30' + CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.3.32' run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" - name: Execute GitHub Copilot CLI @@ -926,7 +937,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.grafana.net\",\"*.sentry.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\",\"awmg-cli-proxy\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.20/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.grafana.net\",\"*.sentry.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\",\"awmg-cli-proxy\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.20\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -935,8 +946,8 @@ jobs: fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then - GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" - GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix ${RUNNER_TEMP}/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH="${RUNNER_TEMP}/gh-aw" GH_AW_CHROOT_IDENTITY_HOME="${RUNNER_TEMP}/gh-aw/home" node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" @@ -945,8 +956,8 @@ jobs: GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi fi - # shellcheck disable=SC1003,SC2086 - awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + # shellcheck disable=SC1003,SC2016,SC2086 + awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_WORKSPACE_NODE_MODULES="${GITHUB_WORKSPACE:-$PWD}/node_modules"; if [ -d "$GH_AW_WORKSPACE_NODE_MODULES" ]; then export NODE_PATH="${GH_AW_WORKSPACE_NODE_MODULES}${NODE_PATH:+:${NODE_PATH}}"; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_sdk_driver.cjs" /usr/local/bin/copilot' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 @@ -965,7 +976,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 10 - GH_AW_VERSION: v0.81.6 + GH_AW_VERSION: dev GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || github.token }} GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true @@ -1176,9 +1187,17 @@ jobs: tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1187,8 +1206,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1228,7 +1247,7 @@ jobs: [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl mkdir -p /tmp/gh-aw/usage/activity - node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + node "${RUNNER_TEMP}/gh-aw/actions/generate_usage_activity_summary.cjs" find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() @@ -1380,6 +1399,7 @@ jobs: GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }} GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} @@ -1435,9 +1455,17 @@ jobs: detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1446,8 +1474,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1480,7 +1508,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.20 ghcr.io/github/gh-aw-firewall/squid:0.27.20 - name: Check if detection needed id: detection_guard if: always() @@ -1543,11 +1571,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.67 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.20 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1567,7 +1595,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.20/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.20\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1576,10 +1604,10 @@ jobs: fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then - GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" - _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix ${RUNNER_TEMP}/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" - printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" @@ -1588,8 +1616,8 @@ jobs: GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi fi - # shellcheck disable=SC1003,SC2086 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + # shellcheck disable=SC1003,SC2016,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 @@ -1603,7 +1631,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.81.6 + GH_AW_VERSION: dev GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1673,6 +1701,8 @@ jobs: pre_activation: runs-on: ubuntu-slim + permissions: + contents: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: @@ -1682,17 +1712,25 @@ jobs: setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership @@ -1727,9 +1765,17 @@ jobs: env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1738,8 +1784,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -1774,6 +1820,16 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/push_experiment_state.cjs'); await main(); + - name: Restore actions folder + if: always() + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions/setup + sparse-checkout-cone-mode: true + clean: false + persist-credentials: false safe_outputs: needs: @@ -1798,7 +1854,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_ENGINE_VERSION: "1.0.67" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} @@ -1816,9 +1872,17 @@ jobs: process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: + - name: Checkout actions folder + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: github/gh-aw + sparse-checkout: | + actions + clean: false + persist-credentials: false - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@v0.81.6 + uses: ./actions/setup with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1827,8 +1891,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Plan Command" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/plan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.65" - GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_VERSION: "1.0.67" + GH_AW_INFO_AWF_VERSION: "v0.27.20" GH_AW_INFO_ENGINE_ID: "copilot" - name: Mask OTLP telemetry headers run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh" diff --git a/docs/adr/42937-multi-checkout-cross-org-credential-wiring.md b/docs/adr/42937-multi-checkout-cross-org-credential-wiring.md new file mode 100644 index 00000000000..e5a4021bf5f --- /dev/null +++ b/docs/adr/42937-multi-checkout-cross-org-credential-wiring.md @@ -0,0 +1,56 @@ +# ADR-42937: Multi-Checkout Cross-Org Credential Wiring Strategy + +**Date**: 2026-07-02 +**Status**: Draft +**Deciders**: Unknown + +--- + +### Context + +The workflow compiler (`pkg/workflow`) must support workflows that check out repositories from multiple organizations in a single GitHub Actions job. Each organization may require distinct authentication credentials — either a pre-existing Personal Access Token (PAT, via `github-token`) or a GitHub App installation token (via `github-app`). Agentic workflows that span org boundaries are a core use case. The critical constraint is that GitHub Actions step output references (`${{ steps..outputs.token }}`) are forward-only: a step may only reference outputs from steps that have already executed, so any token-minting step must be emitted before the checkout step that consumes it. + +### Decision + +We will use an **index-based, heterogeneous credential wiring strategy** in the workflow compiler. For each entry in the `checkout:` frontmatter list, the compiler will: + +1. If the entry uses `github-token`, inject the PAT directly into the checkout step's `token:` field — no intermediate step is emitted. +2. If the entry uses `github-app`, emit an app-token minting step with ID `checkout-app-token-N` (where `N` is the zero-based list index) *before* the corresponding `actions/checkout` step, scoped to the repository's org component as `owner:`. +3. Emit a checkout manifest as env-vars (`GH_AW_CHECKOUT_MANIFEST_COUNT`, `GH_AW_CHECKOUT_REPO_N`, `GH_AW_CHECKOUT_TOKEN_N`) so that safe-outputs handlers can resolve the correct on-disk path and credential for each checked-out repo without additional network calls. +4. Disable `persist-credentials` on all cross-org checkout steps to prevent credential leakage to subsequent steps. + +### Alternatives Considered + +#### Alternative 1: Single shared cross-org token + +Use one organization-level token (or a GitHub App with broad installation) for all checkouts in the same workflow run. Rejected because it requires over-broad permissions that violate the principle of least privilege, and it cannot support heterogeneous auth types (PATs and GitHub Apps coexisting for different orgs). + +#### Alternative 2: Runtime credential resolution + +Defer credential selection to runtime — pass a credential selector into each checkout step as an input and resolve it dynamically. Rejected because it complicates the execution environment, cannot leverage GitHub Actions' type-safe step output mechanism, and moves credential routing logic out of the compiler where it is most easily audited and tested. + +#### Alternative 3: Flat pre-minting (mint all app tokens before any checkout) + +Emit all `checkout-app-token-N` steps as a block before any `actions/checkout` step, regardless of order. Rejected because it concentrates all credential operations at the start of the job, which makes the compiled YAML harder to audit and does not generalize cleanly when checkout steps are interleaved with other job steps. + +### Consequences + +#### Positive +- Each checkout is authenticated with the minimum required credentials for its target org (least privilege). +- The checkout manifest gives safe-outputs handlers deterministic, zero-network token resolution at runtime. +- The integration test suite (four scenarios: PATs-only, Apps-only, mixed, step ordering) provides compiler regression coverage for all credential combinations. +- Step ordering is enforced at compile time — runtime forward-reference errors on `steps..outputs.token` are structurally impossible. + +#### Negative +- Compiled YAML grows by one step per `github-app` checkout entry, increasing job startup latency slightly for app-heavy workflows. +- Index-based step IDs (`checkout-app-token-N`) are position-sensitive: reordering the `checkout:` list in a workflow definition changes the IDs of emitted steps and breaks any hardcoded references to them in workflow bodies. +- The checkout manifest env-var schema (`GH_AW_CHECKOUT_*`) is a new convention that all safe-outputs handlers must be updated to understand. + +#### Neutral +- `persist-credentials: false` becomes the enforced default for all cross-org checkouts; this is consistent with security best practice but diverges from `actions/checkout`'s own default (`true`). +- Mixed-auth workflows (PATs and GitHub Apps coexisting) are fully supported, but only GitHub App entries incur the extra minting step — PAT entries emit no additional steps. +- The `indexOf` helper used in the ordering test (`TestMultiCheckoutCrossOrgStepOrdering`) operates on raw compiled YAML bytes rather than a parsed AST; this is intentional to test the actual emitted text, not an intermediate representation. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* diff --git a/pkg/workflow/multi_checkout_cross_org_integration_test.go b/pkg/workflow/multi_checkout_cross_org_integration_test.go new file mode 100644 index 00000000000..5ca76cc53ca --- /dev/null +++ b/pkg/workflow/multi_checkout_cross_org_integration_test.go @@ -0,0 +1,422 @@ +//go:build integration + +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestMultiCheckoutCrossOrgGitHubTokens verifies that multiple checkouts targeting +// different organizations, each authenticated with a distinct github-token, produce +// separate checkout steps each with the correct per-org PAT, and that the checkout +// manifest env-vars are wired to the matching tokens. +func TestMultiCheckoutCrossOrgGitHubTokens(t *testing.T) { + tmpDir := testutil.TempDir(t, "multi-checkout-tokens-test") + + content := `--- +name: Multi-checkout cross-org PATs +on: + issues: + types: [opened] +permissions: + contents: read + issues: read + pull-requests: read +engine: copilot +strict: false +checkout: + - repository: org-a/repo-one + path: repos/org-a + github-token: ${{ secrets.ORG_A_PAT }} + - repository: org-b/repo-two + path: repos/org-b + github-token: ${{ secrets.ORG_B_PAT }} + - repository: org-c/repo-three + path: repos/org-c + github-token: ${{ secrets.ORG_C_PAT }} +--- + +# Multi-checkout cross-org PATs +` + mdPath := filepath.Join(tmpDir, "multi-checkout-tokens.md") + require.NoError(t, os.WriteFile(mdPath, []byte(content), 0600)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(mdPath), "Workflow with multiple cross-org github-tokens should compile") + + lockPath := filepath.Join(tmpDir, "multi-checkout-tokens.lock.yml") + compiledBytes, err := os.ReadFile(lockPath) + require.NoError(t, err) + compiled := string(compiledBytes) + + // Each additional checkout step must be emitted with its correct target repository and path. + assert.Contains(t, compiled, "name: Checkout org-a/repo-one into repos/org-a", + "Checkout step for org-a/repo-one must be emitted") + assert.Contains(t, compiled, "name: Checkout org-b/repo-two into repos/org-b", + "Checkout step for org-b/repo-two must be emitted") + assert.Contains(t, compiled, "name: Checkout org-c/repo-three into repos/org-c", + "Checkout step for org-c/repo-three must be emitted") + + // Every cross-org checkout must set persist-credentials: false (no credential leakage). + // We assert the token appears before persist-credentials: false in the expected block. + assert.Contains(t, compiled, "repository: org-a/repo-one", + "org-a checkout must declare its repository") + assert.Contains(t, compiled, "repository: org-b/repo-two", + "org-b checkout must declare its repository") + assert.Contains(t, compiled, "repository: org-c/repo-three", + "org-c checkout must declare its repository") + + // Each checkout must inject its org-specific PAT via token:. + assert.Contains(t, compiled, "token: ${{ secrets.ORG_A_PAT }}", + "org-a checkout must use ORG_A_PAT") + assert.Contains(t, compiled, "token: ${{ secrets.ORG_B_PAT }}", + "org-b checkout must use ORG_B_PAT") + assert.Contains(t, compiled, "token: ${{ secrets.ORG_C_PAT }}", + "org-c checkout must use ORG_C_PAT") + + // All cross-repo checkouts must disable credential persistence. + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-a/repo-one", + "org-a checkout must disable credential persistence") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-b/repo-two", + "org-b checkout must disable credential persistence") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-c/repo-three", + "org-c checkout must disable credential persistence") + + // The checkout manifest must record all three repos so safe-outputs handlers can + // resolve the correct on-disk path and token for each org without network calls. + assert.Contains(t, compiled, `GH_AW_CHECKOUT_MANIFEST_COUNT: "3"`, + "Checkout manifest must declare three entries") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_0: "org-a/repo-one"`, + "Manifest must record org-a/repo-one as entry 0") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_0: "repos/org-a"`, + "Manifest must record repos/org-a as entry 0 path") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_0: ${{ secrets.ORG_A_PAT }}", + "Manifest entry 0 must wire ORG_A_PAT") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_1: "org-b/repo-two"`, + "Manifest must record org-b/repo-two as entry 1") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_1: "repos/org-b"`, + "Manifest must record repos/org-b as entry 1 path") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_1: ${{ secrets.ORG_B_PAT }}", + "Manifest entry 1 must wire ORG_B_PAT") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_2: "org-c/repo-three"`, + "Manifest must record org-c/repo-three as entry 2") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_2: "repos/org-c"`, + "Manifest must record repos/org-c as entry 2 path") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_2: ${{ secrets.ORG_C_PAT }}", + "Manifest entry 2 must wire ORG_C_PAT") + + // No app-token minting steps should be emitted when only github-tokens are used. + assert.NotContains(t, compiled, "id: checkout-app-token-", + "No app-token minting steps should appear for github-token-only checkouts") +} + +// TestMultiCheckoutCrossOrgGitHubApps verifies that multiple checkouts targeting +// different organizations, each authenticated via a distinct github-app, produce a +// separate app-token minting step per checkout, and that each checkout step and the +// manifest correctly reference the minted step output for the corresponding index. +func TestMultiCheckoutCrossOrgGitHubApps(t *testing.T) { + tmpDir := testutil.TempDir(t, "multi-checkout-apps-test") + + content := `--- +name: Multi-checkout cross-org GitHub Apps +on: + issues: + types: [opened] +permissions: + contents: read + issues: read + pull-requests: read +engine: copilot +strict: false +checkout: + - repository: org-a/repo-one + path: repos/org-a + github-app: + app-id: ${{ vars.ORG_A_APP_ID }} + private-key: ${{ secrets.ORG_A_PRIVATE_KEY }} + - repository: org-b/repo-two + path: repos/org-b + github-app: + app-id: ${{ vars.ORG_B_APP_ID }} + private-key: ${{ secrets.ORG_B_PRIVATE_KEY }} +--- + +# Multi-checkout cross-org GitHub Apps +` + mdPath := filepath.Join(tmpDir, "multi-checkout-apps.md") + require.NoError(t, os.WriteFile(mdPath, []byte(content), 0600)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(mdPath), "Workflow with multiple cross-org github-apps should compile") + + lockPath := filepath.Join(tmpDir, "multi-checkout-apps.lock.yml") + compiledBytes, err := os.ReadFile(lockPath) + require.NoError(t, err) + compiled := string(compiledBytes) + + // One app-token minting step must be emitted per github-app checkout, each with a + // unique step ID derived from its position in the checkout list. + assert.Contains(t, compiled, "id: checkout-app-token-0", + "App token minting step for checkout index 0 must be emitted") + assert.Contains(t, compiled, "id: checkout-app-token-1", + "App token minting step for checkout index 1 must be emitted") + + // The minting steps must reference the correct app credentials for each org. + assert.Contains(t, compiled, "client-id: ${{ vars.ORG_A_APP_ID }}", + "App token minting for org-a must use ORG_A_APP_ID") + assert.Contains(t, compiled, "private-key: ${{ secrets.ORG_A_PRIVATE_KEY }}", + "App token minting for org-a must use ORG_A_PRIVATE_KEY") + assert.Contains(t, compiled, "client-id: ${{ vars.ORG_B_APP_ID }}", + "App token minting for org-b must use ORG_B_APP_ID") + assert.Contains(t, compiled, "private-key: ${{ secrets.ORG_B_PRIVATE_KEY }}", + "App token minting for org-b must use ORG_B_PRIVATE_KEY") + + // The owner: field must be derived from the repository's org component so each + // minting step requests a token scoped to the correct organization. + assert.Contains(t, compiled, "owner: org-a", + "App token minting step for checkout 0 must scope to org-a") + assert.Contains(t, compiled, "owner: org-b", + "App token minting step for checkout 1 must scope to org-b") + + // Each checkout step must consume the minted token from its corresponding step output. + assert.Contains(t, compiled, "token: ${{ steps.checkout-app-token-0.outputs.token }}", + "org-a/repo-one checkout must use the minted token from checkout-app-token-0") + assert.Contains(t, compiled, "token: ${{ steps.checkout-app-token-1.outputs.token }}", + "org-b/repo-two checkout must use the minted token from checkout-app-token-1") + + // All cross-org checkout steps must disable credential persistence after cloning. + assert.Contains(t, compiled, "name: Checkout org-a/repo-one into repos/org-a", + "Checkout step for org-a must be emitted") + assert.Contains(t, compiled, "name: Checkout org-b/repo-two into repos/org-b", + "Checkout step for org-b must be emitted") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-a/repo-one", + "org-a checkout must disable credential persistence") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-b/repo-two", + "org-b checkout must disable credential persistence") + + // The checkout manifest must wire the app-minted tokens for each entry so that + // safe-outputs handlers can authenticate against the correct org repo. + assert.Contains(t, compiled, `GH_AW_CHECKOUT_MANIFEST_COUNT: "2"`, + "Checkout manifest must declare two entries") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_0: "org-a/repo-one"`, + "Manifest must record org-a/repo-one as entry 0") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_0: "repos/org-a"`, + "Manifest must record repos/org-a as entry 0 path") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_0: ${{ steps.checkout-app-token-0.outputs.token }}", + "Manifest entry 0 must reference the app-minted token for org-a") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_1: "org-b/repo-two"`, + "Manifest must record org-b/repo-two as entry 1") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_1: "repos/org-b"`, + "Manifest must record repos/org-b as entry 1 path") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_1: ${{ steps.checkout-app-token-1.outputs.token }}", + "Manifest entry 1 must reference the app-minted token for org-b") +} + +// TestMultiCheckoutCrossOrgMixedAuth verifies that a workflow with a mix of +// github-token and github-app checkouts across multiple organizations correctly +// emits only the necessary app-token minting steps (one per github-app entry), +// wires each checkout with its specific credential, and propagates the correct +// token expressions into the checkout manifest for safe-outputs handlers. +func TestMultiCheckoutCrossOrgMixedAuth(t *testing.T) { + tmpDir := testutil.TempDir(t, "multi-checkout-mixed-auth-test") + + content := `--- +name: Multi-checkout cross-org mixed auth +on: + issues: + types: [opened] +permissions: + contents: read + issues: read + pull-requests: read +engine: copilot +strict: false +checkout: + - repository: org-a/repo-one + path: repos/org-a + github-token: ${{ secrets.ORG_A_PAT }} + - repository: org-b/repo-two + path: repos/org-b + github-app: + app-id: ${{ vars.ORG_B_APP_ID }} + private-key: ${{ secrets.ORG_B_PRIVATE_KEY }} + - repository: org-c/repo-three + path: repos/org-c + github-app: + app-id: ${{ vars.ORG_C_APP_ID }} + private-key: ${{ secrets.ORG_C_PRIVATE_KEY }} +--- + +# Multi-checkout cross-org mixed auth +` + mdPath := filepath.Join(tmpDir, "multi-checkout-mixed-auth.md") + require.NoError(t, os.WriteFile(mdPath, []byte(content), 0600)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(mdPath), "Workflow with mixed github-token and github-app should compile") + + lockPath := filepath.Join(tmpDir, "multi-checkout-mixed-auth.lock.yml") + compiledBytes, err := os.ReadFile(lockPath) + require.NoError(t, err) + compiled := string(compiledBytes) + + // The github-token checkout (index 0) must NOT produce an app-token minting step; + // app-token steps are only emitted for github-app entries. + assert.NotContains(t, compiled, "id: checkout-app-token-0", + "No app token minting step for PAT-authenticated checkout at index 0") + + // The two github-app checkouts (indices 1 and 2) must each get a minting step. + assert.Contains(t, compiled, "id: checkout-app-token-1", + "App token minting step for github-app checkout at index 1 must be emitted") + assert.Contains(t, compiled, "id: checkout-app-token-2", + "App token minting step for github-app checkout at index 2 must be emitted") + + // Minting steps must use the correct credentials and org-scoped owners. + assert.Contains(t, compiled, "client-id: ${{ vars.ORG_B_APP_ID }}", + "App token minting for org-b must use ORG_B_APP_ID") + assert.Contains(t, compiled, "private-key: ${{ secrets.ORG_B_PRIVATE_KEY }}", + "App token minting for org-b must use ORG_B_PRIVATE_KEY") + assert.Contains(t, compiled, "owner: org-b", + "App token minting for checkout 1 must scope to org-b") + + assert.Contains(t, compiled, "client-id: ${{ vars.ORG_C_APP_ID }}", + "App token minting for org-c must use ORG_C_APP_ID") + assert.Contains(t, compiled, "private-key: ${{ secrets.ORG_C_PRIVATE_KEY }}", + "App token minting for org-c must use ORG_C_PRIVATE_KEY") + assert.Contains(t, compiled, "owner: org-c", + "App token minting for checkout 2 must scope to org-c") + + // Each checkout step must be emitted and use the correct credential expression. + assert.Contains(t, compiled, "name: Checkout org-a/repo-one into repos/org-a", + "Checkout step for org-a must be emitted") + assert.Contains(t, compiled, "token: ${{ secrets.ORG_A_PAT }}", + "org-a checkout must inject the PAT directly (no intermediate minting step)") + + assert.Contains(t, compiled, "name: Checkout org-b/repo-two into repos/org-b", + "Checkout step for org-b must be emitted") + assert.Contains(t, compiled, "token: ${{ steps.checkout-app-token-1.outputs.token }}", + "org-b checkout must consume the minted token from checkout-app-token-1") + + assert.Contains(t, compiled, "name: Checkout org-c/repo-three into repos/org-c", + "Checkout step for org-c must be emitted") + assert.Contains(t, compiled, "token: ${{ steps.checkout-app-token-2.outputs.token }}", + "org-c checkout must consume the minted token from checkout-app-token-2") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-a/repo-one", + "org-a checkout must disable credential persistence") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-b/repo-two", + "org-b checkout must disable credential persistence") + assert.Contains(t, compiled, "persist-credentials: false\n repository: org-c/repo-three", + "org-c checkout must disable credential persistence") + + // The checkout manifest must carry the correct three entries with their respective + // credential expressions so safe-outputs handlers can authenticate per-repo. + assert.Contains(t, compiled, `GH_AW_CHECKOUT_MANIFEST_COUNT: "3"`, + "Checkout manifest must declare three entries") + + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_0: "org-a/repo-one"`, + "Manifest entry 0 must be org-a/repo-one") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_0: "repos/org-a"`, + "Manifest entry 0 must record repos/org-a") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_0: ${{ secrets.ORG_A_PAT }}", + "Manifest entry 0 must carry the ORG_A_PAT directly") + + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_1: "org-b/repo-two"`, + "Manifest entry 1 must be org-b/repo-two") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_1: "repos/org-b"`, + "Manifest entry 1 must record repos/org-b") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_1: ${{ steps.checkout-app-token-1.outputs.token }}", + "Manifest entry 1 must reference the minted token for org-b") + + assert.Contains(t, compiled, `GH_AW_CHECKOUT_REPO_2: "org-c/repo-three"`, + "Manifest entry 2 must be org-c/repo-three") + assert.Contains(t, compiled, `GH_AW_CHECKOUT_PATH_2: "repos/org-c"`, + "Manifest entry 2 must record repos/org-c") + assert.Contains(t, compiled, "GH_AW_CHECKOUT_TOKEN_2: ${{ steps.checkout-app-token-2.outputs.token }}", + "Manifest entry 2 must reference the minted token for org-c") + + // App-token minting steps must appear before their corresponding checkout steps. + mintIdx1 := strings.Index(compiled, "id: checkout-app-token-1") + mintIdx2 := strings.Index(compiled, "id: checkout-app-token-2") + checkoutOrgB := strings.Index(compiled, "name: Checkout org-b/repo-two into repos/org-b") + checkoutOrgC := strings.Index(compiled, "name: Checkout org-c/repo-three into repos/org-c") + require.Greater(t, mintIdx1, -1, "checkout-app-token-1 minting step must be present") + require.Greater(t, mintIdx2, -1, "checkout-app-token-2 minting step must be present") + require.Greater(t, checkoutOrgB, -1, "Checkout step for org-b must be present") + require.Greater(t, checkoutOrgC, -1, "Checkout step for org-c must be present") + assert.Less(t, mintIdx1, checkoutOrgB, + "checkout-app-token-1 minting step must appear before the org-b checkout step") + assert.Less(t, mintIdx2, checkoutOrgC, + "checkout-app-token-2 minting step must appear before the org-c checkout step") +} + +// TestMultiCheckoutCrossOrgStepOrdering verifies that app-token minting steps are +// emitted BEFORE their corresponding checkout steps so that each checkout can +// reference the minted token in its token: field without a forward-reference error. +func TestMultiCheckoutCrossOrgStepOrdering(t *testing.T) { + tmpDir := testutil.TempDir(t, "multi-checkout-ordering-test") + + content := `--- +name: Multi-checkout step ordering +on: + issues: + types: [opened] +permissions: + contents: read + issues: read + pull-requests: read +engine: copilot +strict: false +checkout: + - repository: org-a/repo-one + path: repos/org-a + github-app: + app-id: ${{ vars.ORG_A_APP_ID }} + private-key: ${{ secrets.ORG_A_PRIVATE_KEY }} + - repository: org-b/repo-two + path: repos/org-b + github-app: + app-id: ${{ vars.ORG_B_APP_ID }} + private-key: ${{ secrets.ORG_B_PRIVATE_KEY }} +--- + +# Multi-checkout step ordering +` + mdPath := filepath.Join(tmpDir, "multi-checkout-ordering.md") + require.NoError(t, os.WriteFile(mdPath, []byte(content), 0600)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(mdPath), "Workflow should compile successfully") + + lockPath := filepath.Join(tmpDir, "multi-checkout-ordering.lock.yml") + compiledBytes, err := os.ReadFile(lockPath) + require.NoError(t, err) + compiled := string(compiledBytes) + + // Verify that app-token minting for each checkout index appears BEFORE the + // corresponding checkout step in the compiled output. The token expression + // ${{ steps.checkout-app-token-N.outputs.token }} must resolve at runtime, + // so the minting step must precede the checkout step that consumes its output. + mintIdx0 := strings.Index(compiled, "id: checkout-app-token-0") + mintIdx1 := strings.Index(compiled, "id: checkout-app-token-1") + checkoutOrgA := strings.Index(compiled, "name: Checkout org-a/repo-one into repos/org-a") + checkoutOrgB := strings.Index(compiled, "name: Checkout org-b/repo-two into repos/org-b") + + require.Greater(t, mintIdx0, -1, "checkout-app-token-0 minting step must be present") + require.Greater(t, mintIdx1, -1, "checkout-app-token-1 minting step must be present") + require.Greater(t, checkoutOrgA, -1, "Checkout step for org-a must be present") + require.Greater(t, checkoutOrgB, -1, "Checkout step for org-b must be present") + + assert.Less(t, mintIdx0, checkoutOrgA, + "checkout-app-token-0 minting step must appear before the org-a checkout step") + assert.Less(t, mintIdx1, checkoutOrgB, + "checkout-app-token-1 minting step must appear before the org-b checkout step") + assert.Less(t, mintIdx0, mintIdx1, + "checkout-app-token steps must be emitted in declaration order") +}