From 9c211451e539478214c16eb7c46a9d2473067e8f Mon Sep 17 00:00:00 2001
From: Landon Cox <landon.cox@microsoft.com>
Date: Sat, 4 Apr 2026 12:53:34 -0700
Subject: [PATCH] fix: handle partial results from gh aw logs on rate limit

gh aw logs exits non-zero when it hits the GitHub API rate limit
during pagination, discarding all successfully downloaded runs
(163 runs lost in run #23986065459). The JSON output is never
written because the error return at logs_orchestrator.go:173
skips renderLogsJSON entirely.

This workflow-side fix:
- Captures the exit code with || LOGS_EXIT=$? instead of aborting
- Processes whatever partial JSON was written
- Falls back to empty JSON if nothing was captured

The root cause (gh aw logs discarding partial results) is tracked
in issue #24568.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../workflows/copilot-token-audit.lock.yml    | 32 +++++++++----------
 .github/workflows/copilot-token-audit.md      | 20 +++++++++---
 2 files changed, 32 insertions(+), 20 deletions(-)

diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml
index 9d6d60d6c3b..624a0ab1a6d 100644
--- a/.github/workflows/copilot-token-audit.lock.yml
+++ b/.github/workflows/copilot-token-audit.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5e5bbba744e3b4fddbb22ab5feadff35619a7a73a646769cfb1cb977e6bfb05e","strict":true,"agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f311a20415e36036f9cb4a46ad40f0ce6270c3f606e291211c14216838fa2d48","strict":true,"agent_id":"copilot"}
 #    ___                   _   _      
 #   / _ \                 | | (_)     
 #  | |_| | __ _  ___ _ __ | |_ _  ___ 
@@ -153,9 +153,9 @@ jobs:
         run: |
           bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
           {
-          cat << 'GH_AW_PROMPT_66bee06f519be27f_EOF'
+          cat << 'GH_AW_PROMPT_6125d20035c39a13_EOF'
           <system>
-          GH_AW_PROMPT_66bee06f519be27f_EOF
+          GH_AW_PROMPT_6125d20035c39a13_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"
@@ -163,7 +163,7 @@ jobs:
           cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
           cat "${RUNNER_TEMP}/gh-aw/prompts/repo_memory_prompt.md"
           cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
-          cat << 'GH_AW_PROMPT_66bee06f519be27f_EOF'
+          cat << 'GH_AW_PROMPT_6125d20035c39a13_EOF'
           <safe-output-tools>
           Tools: create_discussion, upload_asset, missing_tool, missing_data, noop
           
@@ -197,14 +197,14 @@ jobs:
           {{/if}}
           </github-context>
           
-          GH_AW_PROMPT_66bee06f519be27f_EOF
+          GH_AW_PROMPT_6125d20035c39a13_EOF
           cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
-          cat << 'GH_AW_PROMPT_66bee06f519be27f_EOF'
+          cat << 'GH_AW_PROMPT_6125d20035c39a13_EOF'
           </system>
           {{#runtime-import .github/workflows/shared/reporting.md}}
           {{#runtime-import .github/workflows/shared/python-dataviz.md}}
           {{#runtime-import .github/workflows/copilot-token-audit.md}}
-          GH_AW_PROMPT_66bee06f519be27f_EOF
+          GH_AW_PROMPT_6125d20035c39a13_EOF
           } > "$GH_AW_PROMPT"
       - name: Interpolate variables and render templates
         uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -408,7 +408,7 @@ jobs:
       - env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         name: Download Copilot workflow logs
-        run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\n# Download last 24 hours of Copilot logs as JSON\ngh aw logs \\\n  --engine copilot \\\n  --start-date -1d \\\n  --json \\\n  -c 100 \\\n  > /tmp/gh-aw/token-audit/copilot-logs.json\n\nTOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/copilot-logs.json)\necho \"✅ Downloaded $TOTAL Copilot workflow runs (last 24 hours)\"\n"
+        run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/token-audit\n\n# Download last 24 hours of Copilot logs as JSON\n# Allow partial results — gh aw logs streams incrementally, so even if\n# it hits an API rate limit partway through, the JSON written so far is\n# still valid and should be processed by the agent.\nLOGS_EXIT=0\ngh aw logs \\\n  --engine copilot \\\n  --start-date -1d \\\n  --json \\\n  -c 100 \\\n  > /tmp/gh-aw/token-audit/copilot-logs.json || LOGS_EXIT=$?\n\nif [ -s /tmp/gh-aw/token-audit/copilot-logs.json ]; then\n  TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/copilot-logs.json)\n  echo \"✅ Downloaded $TOTAL Copilot workflow runs (last 24 hours)\"\n  if [ \"$LOGS_EXIT\" -ne 0 ]; then\n    echo \"⚠️ gh aw logs exited with code $LOGS_EXIT (partial results — likely API rate limit)\"\n  fi\nelse\n  echo \"❌ No log data downloaded (exit code $LOGS_EXIT)\"\n  echo '{\"runs\":[],\"summary\":{}}' > /tmp/gh-aw/token-audit/copilot-logs.json\nfi\n"
 
       # Cache memory file share configuration from frontmatter processed below
       - name: Create cache-memory directory
@@ -508,12 +508,12 @@ 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_8b80532612753551_EOF'
+          cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_75c4ce050a0ea57a_EOF'
           {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1,"title_prefix":"[copilot-token-audit] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg"],"branch":"assets/${{ github.workflow }}","max-size":10240}}
-          GH_AW_SAFE_OUTPUTS_CONFIG_8b80532612753551_EOF
+          GH_AW_SAFE_OUTPUTS_CONFIG_75c4ce050a0ea57a_EOF
       - name: Write Safe Outputs Tools
         run: |
-          cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_1cb33b140f1ad090_EOF'
+          cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_24ed1afa67f7901e_EOF'
           {
             "description_suffixes": {
               "create_discussion": " CONSTRAINTS: Maximum 1 discussion(s) can be created. Title will be prefixed with \"[copilot-token-audit] \". Discussions will be created in category \"audits\".",
@@ -522,8 +522,8 @@ jobs:
             "repo_params": {},
             "dynamic_tools": []
           }
-          GH_AW_SAFE_OUTPUTS_TOOLS_META_1cb33b140f1ad090_EOF
-          cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_ba0302e44a1c3d1e_EOF'
+          GH_AW_SAFE_OUTPUTS_TOOLS_META_24ed1afa67f7901e_EOF
+          cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_6879bccfef4859d0_EOF'
           {
             "create_discussion": {
               "defaultMax": 1,
@@ -618,7 +618,7 @@ jobs:
               }
             }
           }
-          GH_AW_SAFE_OUTPUTS_VALIDATION_ba0302e44a1c3d1e_EOF
+          GH_AW_SAFE_OUTPUTS_VALIDATION_6879bccfef4859d0_EOF
           node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
       - name: Generate Safe Outputs MCP Server Config
         id: safe-outputs-config
@@ -692,7 +692,7 @@ jobs:
           export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/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 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 -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.2.12'
           
           mkdir -p /home/runner/.copilot
-          cat << GH_AW_MCP_CONFIG_759d72ab9f994189_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+          cat << GH_AW_MCP_CONFIG_606521f4ea5282e7_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
           {
             "mcpServers": {
               "agenticworkflows": {
@@ -752,7 +752,7 @@ jobs:
               "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
             }
           }
-          GH_AW_MCP_CONFIG_759d72ab9f994189_EOF
+          GH_AW_MCP_CONFIG_606521f4ea5282e7_EOF
       - name: Download activation artifact
         uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
         with:
diff --git a/.github/workflows/copilot-token-audit.md b/.github/workflows/copilot-token-audit.md
index 95d01110325..ed511e8bc1f 100644
--- a/.github/workflows/copilot-token-audit.md
+++ b/.github/workflows/copilot-token-audit.md
@@ -32,15 +32,27 @@ steps:
       mkdir -p /tmp/gh-aw/token-audit
 
       # Download last 24 hours of Copilot logs as JSON
+      # Allow partial results — gh aw logs streams incrementally, so even if
+      # it hits an API rate limit partway through, the JSON written so far is
+      # still valid and should be processed by the agent.
+      LOGS_EXIT=0
       gh aw logs \
         --engine copilot \
         --start-date -1d \
         --json \
         -c 100 \
-        > /tmp/gh-aw/token-audit/copilot-logs.json
-
-      TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/copilot-logs.json)
-      echo "✅ Downloaded $TOTAL Copilot workflow runs (last 24 hours)"
+        > /tmp/gh-aw/token-audit/copilot-logs.json || LOGS_EXIT=$?
+
+      if [ -s /tmp/gh-aw/token-audit/copilot-logs.json ]; then
+        TOTAL=$(jq '.runs | length' /tmp/gh-aw/token-audit/copilot-logs.json)
+        echo "✅ Downloaded $TOTAL Copilot workflow runs (last 24 hours)"
+        if [ "$LOGS_EXIT" -ne 0 ]; then
+          echo "⚠️ gh aw logs exited with code $LOGS_EXIT (partial results — likely API rate limit)"
+        fi
+      else
+        echo "❌ No log data downloaded (exit code $LOGS_EXIT)"
+        echo '{"runs":[],"summary":{}}' > /tmp/gh-aw/token-audit/copilot-logs.json
+      fi
 timeout-minutes: 25
 imports:
   - uses: shared/daily-audit-discussion.md
