From 30608f0309275db6f414d651137358e7089dfaa6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 10:22:31 +0000
Subject: [PATCH 1/7] Initial plan


From 81c53f8ddbdaa11de54e412a6b7e1b3efdeafb21 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 10:45:10 +0000
Subject: [PATCH 2/7] Fix recurring 60s timeout in logs MCP tool degrading
 audit datasets

- Add engine.mcp.tool-timeout: 10m to safe-output-health.md so the MCP
  gateway allows enough time for the logs subprocess to complete
- Make the logs MCP tool handler context-deadline-aware: compute a bounded
  internal --timeout that leaves a 30s buffer for the subprocess to write
  continuation data before the MCP gateway context expires
- Improve error message when subprocess is killed by context deadline exceeded
  to guide users to set engine.mcp.tool-timeout
- Fix documentation: timeout parameter is in minutes, not seconds
- Add tests for effectiveMCPLogsToolTimeoutMinutesWithDeadline

Closes #45510

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 .github/workflows/safe-output-health.lock.yml |  7 +-
 .github/workflows/safe-output-health.md       |  5 +-
 .../docs/reference/gh-aw-as-mcp-server.md     |  2 +-
 pkg/cli/logs_timeout_test.go                  | 90 +++++++++++++++++++
 pkg/cli/mcp_tools_privileged.go               | 66 +++++++++++++-
 5 files changed, 163 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml
index 8ad36eb31b7..1d697c383e6 100644
--- a/.github/workflows/safe-output-health.lock.yml
+++ b/.github/workflows/safe-output-health.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"64f79551fff15322662b124f9b0205195c60dd747b70ea2cb5be8585f4731c1c","body_hash":"2c461e27e47a032747bca83012c389b7240693fdfd9d9874e490dfcb698776f2","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.207"}}
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"27f7292f1428aa5577d6f4cbbfee5ac57a7da4d0789143db02895a0882aa63da","body_hash":"2c461e27e47a032747bca83012c389b7240693fdfd9d9874e490dfcb698776f2","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.207"}}
 # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","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-go","sha":"924ae3a1cded613372ab5595356fb5720e22ba16","version":"v6.5.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"f9f3042f7e2789586610d6e8b85c8f03e5195baf","version":"v7.2.0"},{"repo":"docker/setup-buildx-action","sha":"d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5","version":"v4.1.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.31","digest":"sha256:84d861cb6da723ac10b7a00dddf778be681b8cd74b2091f18ce1d67fe4b3e7a1","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.31@sha256:84d861cb6da723ac10b7a00dddf778be681b8cd74b2091f18ce1d67fe4b3e7a1"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.31","digest":"sha256:80d982fe7925c640d76cbbfbe94081d2d34f7657b7c37494d8d5488f5dae3c63","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.31@sha256:80d982fe7925c640d76cbbfbe94081d2d34f7657b7c37494d8d5488f5dae3c63"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.31","digest":"sha256:c05a3f086946fab0833e078f46d35571080f187ca72f038958d45aa5cc150494","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.31@sha256:c05a3f086946fab0833e078f46d35571080f187ca72f038958d45aa5cc150494"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"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
 #
@@ -804,7 +804,7 @@ jobs:
           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 --add-host host.docker.internal:host-gateway --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.4.1'
 
           GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
-          cat << GH_AW_MCP_CONFIG_c2ec0bf1aaf37de7_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+          cat << GH_AW_MCP_CONFIG_3331d52fe463a4d6_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
           {
             "mcpServers": {
               "agenticworkflows": {
@@ -879,6 +879,7 @@ jobs:
               "domain": "${MCP_GATEWAY_DOMAIN}",
               "apiKey": "${MCP_GATEWAY_API_KEY}",
               "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
+              "toolTimeout": 600,
               "opentelemetry": {
                 "endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
                 "traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
@@ -886,7 +887,7 @@ jobs:
               }
             }
           }
-          GH_AW_MCP_CONFIG_c2ec0bf1aaf37de7_EOF
+          GH_AW_MCP_CONFIG_3331d52fe463a4d6_EOF
       - name: Mount MCP servers as CLIs
         id: mount-mcp-clis
         continue-on-error: true
diff --git a/.github/workflows/safe-output-health.md b/.github/workflows/safe-output-health.md
index ea4cd72c799..960c839103a 100644
--- a/.github/workflows/safe-output-health.md
+++ b/.github/workflows/safe-output-health.md
@@ -15,7 +15,10 @@ sandbox:
   agent:
     sudo: false
 
-engine: claude
+engine:
+  id: claude
+  mcp:
+    tool-timeout: 10m
 max-ai-credits: 1500
 tools:
   cli-proxy: true
diff --git a/docs/src/content/docs/reference/gh-aw-as-mcp-server.md b/docs/src/content/docs/reference/gh-aw-as-mcp-server.md
index ecec429a3dd..9d271af021b 100644
--- a/docs/src/content/docs/reference/gh-aw-as-mcp-server.md
+++ b/docs/src/content/docs/reference/gh-aw-as-mcp-server.md
@@ -142,7 +142,7 @@ Download and analyze workflow logs with timeout handling and size guardrails.
 - `engine`, `firewall`, `no_firewall`, `branch` (optional): Run filters
 - `after_run_id`, `before_run_id` (optional): Pagination by run ID
 - `artifacts` (array of strings, optional): Artifact sets to download. Valid values: `all`, `activation`, `agent`, `detection`, `experiment`, `firewall`, `github-api`, `mcp`, `usage`. Defaults to `usage`.
-- `timeout` (optional): Max seconds to download (default: 50)
+- `timeout` (optional): Max minutes to download (default: auto-scales with count in the MCP server, rounded up in 40-run increments; e.g. 1 minute up to 40 runs, 2 minutes for 41-80, 3 minutes for 81-120). For large date-range fetches set this explicitly (e.g. `5`) and also configure `engine.mcp.tool-timeout: 10m` in the workflow frontmatter so the MCP gateway allows enough time.
 - `max_tokens` (optional): Output token guardrail (default: 12000)
 - `jq` (optional): Apply jq filter to JSON output
 
diff --git a/pkg/cli/logs_timeout_test.go b/pkg/cli/logs_timeout_test.go
index 9a07d2543aa..14599ae7242 100644
--- a/pkg/cli/logs_timeout_test.go
+++ b/pkg/cli/logs_timeout_test.go
@@ -3,6 +3,7 @@
 package cli
 
 import (
+	"context"
 	"testing"
 	"time"
 )
@@ -171,3 +172,92 @@ func TestEffectiveMCPLogsToolTimeoutMinutes(t *testing.T) {
 		})
 	}
 }
+
+// TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline verifies that the deadline-aware
+// variant caps the subprocess timeout to leave room for output writing before the
+// MCP gateway context expires.
+func TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline(t *testing.T) {
+	tests := []struct {
+		name             string
+		requestedTimeout int
+		count            int
+		deadlineIn       time.Duration // 0 = no deadline (context.Background)
+		want             int
+	}{
+		{
+			name:             "no context deadline returns base auto-scale timeout",
+			requestedTimeout: 0,
+			count:            100,
+			deadlineIn:       0, // no deadline
+			want:             3, // defaultMCPLogsToolTimeoutMinutesForCount(100)
+		},
+		{
+			name:             "explicit timeout preserved when no deadline",
+			requestedTimeout: 5,
+			count:            100,
+			deadlineIn:       0,
+			want:             5,
+		},
+		{
+			name:             "generous deadline does not cap base timeout",
+			requestedTimeout: 0,
+			count:            100,
+			deadlineIn:       10 * time.Minute, // 10m − 30s buffer = 9m > base 3m
+			want:             3,
+		},
+		{
+			name:             "deadline just above buffer caps to floor of usable minutes",
+			requestedTimeout: 0,
+			count:            100,
+			// 2m45s − 30s buffer = 2m15s → int(2.25) = 2 < base 3m → cap to 2.
+			// The extra 15s over 2m30s guards against ms of test execution overhead.
+			deadlineIn: 2*time.Minute + 45*time.Second,
+			want:       2,
+		},
+		{
+			name:             "deadline yields exactly one usable minute",
+			requestedTimeout: 0,
+			count:            100,
+			// 95s − 30s buffer = 65s → int(1.08) = 1 < base 3m → cap to 1.
+			// Using 95s instead of 90s guards against ms of test execution overhead.
+			deadlineIn: 95 * time.Second,
+			want:       1,
+		},
+		{
+			name:             "deadline too short for buffer leaves base timeout unchanged",
+			requestedTimeout: 0,
+			count:            100,
+			// 60s − 30s buffer = 30s → deadlineMinutes=0 → no cap applied
+			deadlineIn: 60 * time.Second,
+			want:       3,
+		},
+		{
+			name:             "explicit timeout also capped by deadline",
+			requestedTimeout: 10,
+			count:            100,
+			// 3m45s − 30s = 3m15s → int(3.25) = 3 < explicit 10m → cap to 3.
+			// The extra 15s guards against ms of test execution overhead.
+			deadlineIn: 3*time.Minute + 45*time.Second,
+			want:       3,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var ctx context.Context
+			if tt.deadlineIn > 0 {
+				var cancel context.CancelFunc
+				ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(tt.deadlineIn))
+				defer cancel()
+			} else {
+				ctx = context.Background()
+			}
+
+			got := effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx, tt.requestedTimeout, tt.count)
+			if got != tt.want {
+				t.Errorf("effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx[deadline=%v], %d, %d) = %d, want %d",
+					tt.deadlineIn, tt.requestedTimeout, tt.count, got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go
index f2cb0450c93..54946236e5f 100644
--- a/pkg/cli/mcp_tools_privileged.go
+++ b/pkg/cli/mcp_tools_privileged.go
@@ -9,6 +9,7 @@ import (
 	"os/exec"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/github/gh-aw/pkg/constants"
 	"github.com/modelcontextprotocol/go-sdk/jsonrpc"
@@ -19,6 +20,12 @@ const (
 	defaultMCPLogsToolCount            = 100
 	defaultMCPLogsTimeoutMinutes       = 1
 	mcpLogsRunsPerDefaultTimeoutMinute = 40
+
+	// mcpLogsDeadlineBufferSeconds is the margin subtracted from the context
+	// deadline when computing the subprocess timeout. It gives the subprocess
+	// time to finish writing its JSON output (including continuation data)
+	// before the MCP gateway context is cancelled.
+	mcpLogsDeadlineBufferSeconds = 30
 )
 
 // appendRepoFlagFromEnv appends "--repo <owner/repo>" to args when GITHUB_REPOSITORY
@@ -75,6 +82,51 @@ func effectiveMCPLogsToolTimeoutMinutes(requestedTimeout, count int) int {
 	return defaultMCPLogsToolTimeoutMinutesForCount(count)
 }
 
+// effectiveMCPLogsToolTimeoutMinutesWithDeadline returns the internal subprocess
+// timeout (in minutes), bounded by any deadline on ctx.
+//
+// When the context carries a deadline the timeout is capped so the subprocess
+// can finish writing its output (including continuation data) before the MCP
+// gateway context expires. mcpLogsDeadlineBufferSeconds is subtracted from the
+// remaining time to give the subprocess a window to complete output writing.
+//
+// Examples (with mcpLogsDeadlineBufferSeconds = 30s):
+//
+//	ctx has 10m deadline, count=100 (auto=3m) → 3m  (deadline provides ≥4m, no cap needed)
+//	ctx has  2m deadline, count=100 (auto=3m) → 1m  (floor((2m-30s)/1m)=1, caps base 3m)
+//	ctx has 60s deadline, count=100 (auto=3m) → 3m  (floor((60s-30s)/1m)=0 < 1; no cap)
+func effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx context.Context, requestedTimeout, count int) int {
+	baseTimeout := effectiveMCPLogsToolTimeoutMinutes(requestedTimeout, count)
+
+	deadline, ok := ctx.Deadline()
+	if !ok {
+		return baseTimeout
+	}
+
+	remaining := time.Until(deadline)
+	constrained := remaining - mcpLogsDeadlineBufferSeconds*time.Second
+	if constrained <= 0 {
+		// Already at or past the buffer point — no useful adjustment possible.
+		return baseTimeout
+	}
+
+	deadlineMinutes := int(constrained.Minutes())
+	if deadlineMinutes < 1 {
+		// Less than 1 full minute available after the buffer; the subprocess
+		// cannot reliably complete in time regardless of the timeout value.
+		// Return the base timeout as-is and let the context kill the subprocess
+		// if needed. The caller's error path will surface a helpful message.
+		return baseTimeout
+	}
+
+	if deadlineMinutes < baseTimeout {
+		mcpLog.Printf("Bounding logs subprocess timeout to %d min (context deadline in %.0fs, %ds buffer)",
+			deadlineMinutes, remaining.Seconds(), mcpLogsDeadlineBufferSeconds)
+		return deadlineMinutes
+	}
+	return baseTimeout
+}
+
 // The logs tool requires write+ access and checks actor permissions.
 // Returns an error if schema generation fails.
 func registerLogsTool(server *mcp.Server, execCmd execCmdFunc, actor string, validateActor bool) error {
@@ -208,8 +260,10 @@ from where the previous request stopped due to timeout.`,
 		cmdArgs = appendRepoFlagFromEnv(cmdArgs)
 
 		// Scale the implicit MCP timeout with the requested fetch window so
-		// larger fleet-wide requests do not hit the 60s server deadline by default.
-		timeoutValue := effectiveMCPLogsToolTimeoutMinutes(args.Timeout, effectiveCount)
+		// larger fleet-wide requests do not hit the MCP gateway deadline.
+		// Also bound to the context deadline (minus a buffer) so the subprocess
+		// can write continuation data before the gateway cancels the request.
+		timeoutValue := effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx, args.Timeout, effectiveCount)
 		cmdArgs = append(cmdArgs, "--timeout", strconv.Itoa(timeoutValue))
 
 		// Always use --json mode in MCP server
@@ -262,6 +316,14 @@ from where the previous request stopped due to timeout.`,
 			if mainMsg == "" {
 				mainMsg = err.Error()
 			}
+
+			// When the subprocess was killed because the MCP gateway timeout fired,
+			// add actionable guidance so callers know how to extend the deadline.
+			if errors.Is(ctx.Err(), context.DeadlineExceeded) ||
+				strings.Contains(err.Error(), "context deadline exceeded") {
+				mainMsg += "\n\nThe logs tool exceeded the MCP gateway tool timeout. " +
+					"Add 'engine.mcp.tool-timeout: 10m' (or higher) to your workflow frontmatter to allow more time."
+			}
 			return nil, nil, newMCPError(jsonrpc.CodeInternalError, "failed to download workflow logs: "+mainMsg, errorData)
 		}
 

From ca436dd2d0aa8d4381a4352755e74bf150ff78bd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 10:50:06 +0000
Subject: [PATCH 3/7] Clarify test comments: use truncate() notation for int()
 floor truncation

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/cli/logs_timeout_test.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pkg/cli/logs_timeout_test.go b/pkg/cli/logs_timeout_test.go
index 14599ae7242..4aea51b9306 100644
--- a/pkg/cli/logs_timeout_test.go
+++ b/pkg/cli/logs_timeout_test.go
@@ -209,7 +209,7 @@ func TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline(t *testing.T) {
 			name:             "deadline just above buffer caps to floor of usable minutes",
 			requestedTimeout: 0,
 			count:            100,
-			// 2m45s − 30s buffer = 2m15s → int(2.25) = 2 < base 3m → cap to 2.
+			// 2m45s − 30s buffer = 2m15s = 2.25min → truncate(2.25) = 2 < base 3m → cap to 2.
 			// The extra 15s over 2m30s guards against ms of test execution overhead.
 			deadlineIn: 2*time.Minute + 45*time.Second,
 			want:       2,
@@ -218,7 +218,7 @@ func TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline(t *testing.T) {
 			name:             "deadline yields exactly one usable minute",
 			requestedTimeout: 0,
 			count:            100,
-			// 95s − 30s buffer = 65s → int(1.08) = 1 < base 3m → cap to 1.
+			// 95s − 30s buffer = 65s = 1.083min → truncate(1.083) = 1 < base 3m → cap to 1.
 			// Using 95s instead of 90s guards against ms of test execution overhead.
 			deadlineIn: 95 * time.Second,
 			want:       1,
@@ -227,7 +227,7 @@ func TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline(t *testing.T) {
 			name:             "deadline too short for buffer leaves base timeout unchanged",
 			requestedTimeout: 0,
 			count:            100,
-			// 60s − 30s buffer = 30s → deadlineMinutes=0 → no cap applied
+			// 60s − 30s buffer = 30s = 0.5min → truncate(0.5) = 0 → no cap applied
 			deadlineIn: 60 * time.Second,
 			want:       3,
 		},
@@ -235,7 +235,7 @@ func TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline(t *testing.T) {
 			name:             "explicit timeout also capped by deadline",
 			requestedTimeout: 10,
 			count:            100,
-			// 3m45s − 30s = 3m15s → int(3.25) = 3 < explicit 10m → cap to 3.
+			// 3m45s − 30s = 3m15s = 3.25min → truncate(3.25) = 3 < explicit 10m → cap to 3.
 			// The extra 15s guards against ms of test execution overhead.
 			deadlineIn: 3*time.Minute + 45*time.Second,
 			want:       3,

From 0026d772b7e5f522ac4d4ff88ebd610af1548451 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 12:46:46 +0000
Subject: [PATCH 4/7] Adjust logs timeout example value in MCP server reference

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 docs/src/content/docs/reference/gh-aw-as-mcp-server.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/src/content/docs/reference/gh-aw-as-mcp-server.md b/docs/src/content/docs/reference/gh-aw-as-mcp-server.md
index 9d271af021b..f57232e2c78 100644
--- a/docs/src/content/docs/reference/gh-aw-as-mcp-server.md
+++ b/docs/src/content/docs/reference/gh-aw-as-mcp-server.md
@@ -142,7 +142,7 @@ Download and analyze workflow logs with timeout handling and size guardrails.
 - `engine`, `firewall`, `no_firewall`, `branch` (optional): Run filters
 - `after_run_id`, `before_run_id` (optional): Pagination by run ID
 - `artifacts` (array of strings, optional): Artifact sets to download. Valid values: `all`, `activation`, `agent`, `detection`, `experiment`, `firewall`, `github-api`, `mcp`, `usage`. Defaults to `usage`.
-- `timeout` (optional): Max minutes to download (default: auto-scales with count in the MCP server, rounded up in 40-run increments; e.g. 1 minute up to 40 runs, 2 minutes for 41-80, 3 minutes for 81-120). For large date-range fetches set this explicitly (e.g. `5`) and also configure `engine.mcp.tool-timeout: 10m` in the workflow frontmatter so the MCP gateway allows enough time.
+- `timeout` (optional): Max minutes to download (default: auto-scales with count in the MCP server, rounded up in 40-run increments; e.g. 1 minute up to 40 runs, 2 minutes for 41-80, 3 minutes for 81-120). For large date-range fetches set this explicitly (e.g. `10`) and also configure `engine.mcp.tool-timeout: 10m` in the workflow frontmatter so the MCP gateway allows enough time.
 - `max_tokens` (optional): Output token guardrail (default: 12000)
 - `jq` (optional): Apply jq filter to JSON output
 

From 952f8b6432486c447833f1b1c617e7bba449d931 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 14:09:30 +0000
Subject: [PATCH 5/7] Remove ineffective deadline-based logs timeout handling

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
---
 pkg/cli/logs_timeout_test.go    | 90 ---------------------------------
 pkg/cli/mcp_tools_privileged.go | 65 +-----------------------
 2 files changed, 2 insertions(+), 153 deletions(-)

diff --git a/pkg/cli/logs_timeout_test.go b/pkg/cli/logs_timeout_test.go
index 4aea51b9306..9a07d2543aa 100644
--- a/pkg/cli/logs_timeout_test.go
+++ b/pkg/cli/logs_timeout_test.go
@@ -3,7 +3,6 @@
 package cli
 
 import (
-	"context"
 	"testing"
 	"time"
 )
@@ -172,92 +171,3 @@ func TestEffectiveMCPLogsToolTimeoutMinutes(t *testing.T) {
 		})
 	}
 }
-
-// TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline verifies that the deadline-aware
-// variant caps the subprocess timeout to leave room for output writing before the
-// MCP gateway context expires.
-func TestEffectiveMCPLogsToolTimeoutMinutesWithDeadline(t *testing.T) {
-	tests := []struct {
-		name             string
-		requestedTimeout int
-		count            int
-		deadlineIn       time.Duration // 0 = no deadline (context.Background)
-		want             int
-	}{
-		{
-			name:             "no context deadline returns base auto-scale timeout",
-			requestedTimeout: 0,
-			count:            100,
-			deadlineIn:       0, // no deadline
-			want:             3, // defaultMCPLogsToolTimeoutMinutesForCount(100)
-		},
-		{
-			name:             "explicit timeout preserved when no deadline",
-			requestedTimeout: 5,
-			count:            100,
-			deadlineIn:       0,
-			want:             5,
-		},
-		{
-			name:             "generous deadline does not cap base timeout",
-			requestedTimeout: 0,
-			count:            100,
-			deadlineIn:       10 * time.Minute, // 10m − 30s buffer = 9m > base 3m
-			want:             3,
-		},
-		{
-			name:             "deadline just above buffer caps to floor of usable minutes",
-			requestedTimeout: 0,
-			count:            100,
-			// 2m45s − 30s buffer = 2m15s = 2.25min → truncate(2.25) = 2 < base 3m → cap to 2.
-			// The extra 15s over 2m30s guards against ms of test execution overhead.
-			deadlineIn: 2*time.Minute + 45*time.Second,
-			want:       2,
-		},
-		{
-			name:             "deadline yields exactly one usable minute",
-			requestedTimeout: 0,
-			count:            100,
-			// 95s − 30s buffer = 65s = 1.083min → truncate(1.083) = 1 < base 3m → cap to 1.
-			// Using 95s instead of 90s guards against ms of test execution overhead.
-			deadlineIn: 95 * time.Second,
-			want:       1,
-		},
-		{
-			name:             "deadline too short for buffer leaves base timeout unchanged",
-			requestedTimeout: 0,
-			count:            100,
-			// 60s − 30s buffer = 30s = 0.5min → truncate(0.5) = 0 → no cap applied
-			deadlineIn: 60 * time.Second,
-			want:       3,
-		},
-		{
-			name:             "explicit timeout also capped by deadline",
-			requestedTimeout: 10,
-			count:            100,
-			// 3m45s − 30s = 3m15s = 3.25min → truncate(3.25) = 3 < explicit 10m → cap to 3.
-			// The extra 15s guards against ms of test execution overhead.
-			deadlineIn: 3*time.Minute + 45*time.Second,
-			want:       3,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			var ctx context.Context
-			if tt.deadlineIn > 0 {
-				var cancel context.CancelFunc
-				ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(tt.deadlineIn))
-				defer cancel()
-			} else {
-				ctx = context.Background()
-			}
-
-			got := effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx, tt.requestedTimeout, tt.count)
-			if got != tt.want {
-				t.Errorf("effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx[deadline=%v], %d, %d) = %d, want %d",
-					tt.deadlineIn, tt.requestedTimeout, tt.count, got, tt.want)
-			}
-		})
-	}
-}
diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go
index 54946236e5f..3ab9282b644 100644
--- a/pkg/cli/mcp_tools_privileged.go
+++ b/pkg/cli/mcp_tools_privileged.go
@@ -9,7 +9,6 @@ import (
 	"os/exec"
 	"strconv"
 	"strings"
-	"time"
 
 	"github.com/github/gh-aw/pkg/constants"
 	"github.com/modelcontextprotocol/go-sdk/jsonrpc"
@@ -20,12 +19,6 @@ const (
 	defaultMCPLogsToolCount            = 100
 	defaultMCPLogsTimeoutMinutes       = 1
 	mcpLogsRunsPerDefaultTimeoutMinute = 40
-
-	// mcpLogsDeadlineBufferSeconds is the margin subtracted from the context
-	// deadline when computing the subprocess timeout. It gives the subprocess
-	// time to finish writing its JSON output (including continuation data)
-	// before the MCP gateway context is cancelled.
-	mcpLogsDeadlineBufferSeconds = 30
 )
 
 // appendRepoFlagFromEnv appends "--repo <owner/repo>" to args when GITHUB_REPOSITORY
@@ -82,51 +75,6 @@ func effectiveMCPLogsToolTimeoutMinutes(requestedTimeout, count int) int {
 	return defaultMCPLogsToolTimeoutMinutesForCount(count)
 }
 
-// effectiveMCPLogsToolTimeoutMinutesWithDeadline returns the internal subprocess
-// timeout (in minutes), bounded by any deadline on ctx.
-//
-// When the context carries a deadline the timeout is capped so the subprocess
-// can finish writing its output (including continuation data) before the MCP
-// gateway context expires. mcpLogsDeadlineBufferSeconds is subtracted from the
-// remaining time to give the subprocess a window to complete output writing.
-//
-// Examples (with mcpLogsDeadlineBufferSeconds = 30s):
-//
-//	ctx has 10m deadline, count=100 (auto=3m) → 3m  (deadline provides ≥4m, no cap needed)
-//	ctx has  2m deadline, count=100 (auto=3m) → 1m  (floor((2m-30s)/1m)=1, caps base 3m)
-//	ctx has 60s deadline, count=100 (auto=3m) → 3m  (floor((60s-30s)/1m)=0 < 1; no cap)
-func effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx context.Context, requestedTimeout, count int) int {
-	baseTimeout := effectiveMCPLogsToolTimeoutMinutes(requestedTimeout, count)
-
-	deadline, ok := ctx.Deadline()
-	if !ok {
-		return baseTimeout
-	}
-
-	remaining := time.Until(deadline)
-	constrained := remaining - mcpLogsDeadlineBufferSeconds*time.Second
-	if constrained <= 0 {
-		// Already at or past the buffer point — no useful adjustment possible.
-		return baseTimeout
-	}
-
-	deadlineMinutes := int(constrained.Minutes())
-	if deadlineMinutes < 1 {
-		// Less than 1 full minute available after the buffer; the subprocess
-		// cannot reliably complete in time regardless of the timeout value.
-		// Return the base timeout as-is and let the context kill the subprocess
-		// if needed. The caller's error path will surface a helpful message.
-		return baseTimeout
-	}
-
-	if deadlineMinutes < baseTimeout {
-		mcpLog.Printf("Bounding logs subprocess timeout to %d min (context deadline in %.0fs, %ds buffer)",
-			deadlineMinutes, remaining.Seconds(), mcpLogsDeadlineBufferSeconds)
-		return deadlineMinutes
-	}
-	return baseTimeout
-}
-
 // The logs tool requires write+ access and checks actor permissions.
 // Returns an error if schema generation fails.
 func registerLogsTool(server *mcp.Server, execCmd execCmdFunc, actor string, validateActor bool) error {
@@ -260,10 +208,8 @@ from where the previous request stopped due to timeout.`,
 		cmdArgs = appendRepoFlagFromEnv(cmdArgs)
 
 		// Scale the implicit MCP timeout with the requested fetch window so
-		// larger fleet-wide requests do not hit the MCP gateway deadline.
-		// Also bound to the context deadline (minus a buffer) so the subprocess
-		// can write continuation data before the gateway cancels the request.
-		timeoutValue := effectiveMCPLogsToolTimeoutMinutesWithDeadline(ctx, args.Timeout, effectiveCount)
+		// larger fleet-wide requests do not hit the default per-tool timeout.
+		timeoutValue := effectiveMCPLogsToolTimeoutMinutes(args.Timeout, effectiveCount)
 		cmdArgs = append(cmdArgs, "--timeout", strconv.Itoa(timeoutValue))
 
 		// Always use --json mode in MCP server
@@ -317,13 +263,6 @@ from where the previous request stopped due to timeout.`,
 				mainMsg = err.Error()
 			}
 
-			// When the subprocess was killed because the MCP gateway timeout fired,
-			// add actionable guidance so callers know how to extend the deadline.
-			if errors.Is(ctx.Err(), context.DeadlineExceeded) ||
-				strings.Contains(err.Error(), "context deadline exceeded") {
-				mainMsg += "\n\nThe logs tool exceeded the MCP gateway tool timeout. " +
-					"Add 'engine.mcp.tool-timeout: 10m' (or higher) to your workflow frontmatter to allow more time."
-			}
 			return nil, nil, newMCPError(jsonrpc.CodeInternalError, "failed to download workflow logs: "+mainMsg, errorData)
 		}
 

From 605d1edf85068e10e2e4fc1cc99890bb0d87aca8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 15:22:36 +0000
Subject: [PATCH 6/7] Plan: triage PR and resolve remaining feedback

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
---
 .github/workflows/safe-output-health.lock.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml
index c08966e85da..968879fddd7 100644
--- a/.github/workflows/safe-output-health.lock.yml
+++ b/.github/workflows/safe-output-health.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"27f7292f1428aa5577d6f4cbbfee5ac57a7da4d0789143db02895a0882aa63da","body_hash":"2c461e27e47a032747bca83012c389b7240693fdfd9d9874e490dfcb698776f2","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.207"}}
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"27f7292f1428aa5577d6f4cbbfee5ac57a7da4d0789143db02895a0882aa63da","body_hash":"2c461e27e47a032747bca83012c389b7240693fdfd9d9874e490dfcb698776f2","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.210"}}
 # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","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-go","sha":"924ae3a1cded613372ab5595356fb5720e22ba16","version":"v6.5.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"f9f3042f7e2789586610d6e8b85c8f03e5195baf","version":"v7.2.0"},{"repo":"docker/setup-buildx-action","sha":"d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5","version":"v4.1.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.31","digest":"sha256:84d861cb6da723ac10b7a00dddf778be681b8cd74b2091f18ce1d67fe4b3e7a1","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.31@sha256:84d861cb6da723ac10b7a00dddf778be681b8cd74b2091f18ce1d67fe4b3e7a1"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.31","digest":"sha256:80d982fe7925c640d76cbbfbe94081d2d34f7657b7c37494d8d5488f5dae3c63","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.31@sha256:80d982fe7925c640d76cbbfbe94081d2d34f7657b7c37494d8d5488f5dae3c63"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.31","digest":"sha256:c05a3f086946fab0833e078f46d35571080f187ca72f038958d45aa5cc150494","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.31@sha256:c05a3f086946fab0833e078f46d35571080f187ca72f038958d45aa5cc150494"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"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
 #

From a4c08bfa032f0349792a30f436997b3d4235a42a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Jul 2026 17:33:55 +0000
Subject: [PATCH 7/7] chore: start pr-finisher pass plan

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
---
 .github/workflows/architecture-guardian.lock.yml   | 1 +
 .github/workflows/blog-auditor.lock.yml            | 1 +
 .github/workflows/ci-coach.lock.yml                | 1 +
 .github/workflows/daily-doc-healer.lock.yml        | 1 +
 .github/workflows/daily-issues-report.lock.yml     | 1 +
 .github/workflows/daily-news.lock.yml              | 1 +
 .github/workflows/draft-pr-cleanup.lock.yml        | 1 +
 .github/workflows/plan.lock.yml                    | 1 +
 .github/workflows/stale-pr-cleanup.lock.yml        | 1 +
 .github/workflows/weekly-blog-post-writer.lock.yml | 1 +
 10 files changed, 10 insertions(+)

diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml
index 2e1612ed77a..14bf25b1f9a 100644
--- a/.github/workflows/architecture-guardian.lock.yml
+++ b/.github/workflows/architecture-guardian.lock.yml
@@ -1792,6 +1792,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"violations-analyzed","question":"Did the workflow analyze changed Go or JavaScript files from the last 24 hours for architecture violations, or correctly conclude there were none?"},{"id":"issue-created-or-noop","question":"Was an architecture report issue created when violations were found, or was noop or skip behavior used appropriately when there was nothing actionable?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml
index 535260cc1d3..73044875ffd 100644
--- a/.github/workflows/blog-auditor.lock.yml
+++ b/.github/workflows/blog-auditor.lock.yml
@@ -1857,6 +1857,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"audit-completed","question":"Did the workflow complete the blog availability audit, including page access and content validation checks?"},{"id":"discussion-created","question":"Was an audit discussion created summarizing the pass or fail results and suggested remediation when needed?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml
index 6bf5ab0efcf..d3ab17ed7bd 100644
--- a/.github/workflows/ci-coach.lock.yml
+++ b/.github/workflows/ci-coach.lock.yml
@@ -1843,6 +1843,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"repair-or-optimization-path","question":"Did the workflow check validation-status first and then follow the correct repair or optimization path for this run?"},{"id":"pr-created-or-noop","question":"Was a focused CI improvement pull request created when actionable work was found, or was noop called when CI was already healthy?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml
index b108fc219dd..c7ea55cde8c 100644
--- a/.github/workflows/daily-doc-healer.lock.yml
+++ b/.github/workflows/daily-doc-healer.lock.yml
@@ -1970,6 +1970,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"gaps-confirmed","question":"Did the workflow identify at least one confirmed documentation gap to fix, or correctly conclude that no actionable gap remained?"},{"id":"pr-issue-or-noop","question":"Was a documentation pull request or issue created for confirmed gaps, or was noop used appropriately when nothing required action?"}]'
           GH_AW_EVALS_MODEL: "${{ needs.activation.outputs.model_size }}"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml
index a01527c95c0..4218bec6d89 100644
--- a/.github/workflows/daily-issues-report.lock.yml
+++ b/.github/workflows/daily-issues-report.lock.yml
@@ -2036,6 +2036,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"report-generated","question":"Did the workflow analyze the prefetched issues data and generate a clustered daily issues report with metrics and trends?"},{"id":"discussion-created","question":"Was a daily issues discussion created successfully with the report findings and recommendations?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml
index 64da77c50b2..8783ab81823 100644
--- a/.github/workflows/daily-news.lock.yml
+++ b/.github/workflows/daily-news.lock.yml
@@ -1965,6 +1965,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"digest-generated","question":"Did the workflow analyze the prefetched repository activity and generate a coherent daily news digest?"},{"id":"discussion-created","question":"Was a daily-news discussion created or updated successfully with the report output?"}]'
           GH_AW_EVALS_MODEL: "copilot/gpt-5.4"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/draft-pr-cleanup.lock.yml b/.github/workflows/draft-pr-cleanup.lock.yml
index bc855a84b50..d0a9dc20b9f 100644
--- a/.github/workflows/draft-pr-cleanup.lock.yml
+++ b/.github/workflows/draft-pr-cleanup.lock.yml
@@ -1765,6 +1765,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"stale-drafts-triaged","question":"Did the workflow review open draft pull requests and classify stale drafts according to the warning and cleanup policy?"},{"id":"warnings-or-closures-applied","question":"Were the expected labels, comments, and closures applied to stale draft pull requests when appropriate?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml
index 51073d47135..f4f285d33b0 100644
--- a/.github/workflows/plan.lock.yml
+++ b/.github/workflows/plan.lock.yml
@@ -1904,6 +1904,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"subissues-planned","question":"Did the workflow analyze the triggering issue or discussion and break the work into actionable sub-issues?"},{"id":"issue-actions-completed","question":"Were the planned sub-issues created successfully, with discussion closure handled correctly when required?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/stale-pr-cleanup.lock.yml b/.github/workflows/stale-pr-cleanup.lock.yml
index b13c8de2d8d..0c71624650e 100644
--- a/.github/workflows/stale-pr-cleanup.lock.yml
+++ b/.github/workflows/stale-pr-cleanup.lock.yml
@@ -1754,6 +1754,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"stale-prs-triaged","question":"Did the workflow review pull requests open for 30 or more days and classify them according to the cleanup policy?"},{"id":"actions-taken-or-noop","question":"Were the appropriate stale PR comments, labels, and closures applied when needed, or was noop used correctly when no action was required?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml
index e4616091ae7..77ec897fe2b 100644
--- a/.github/workflows/weekly-blog-post-writer.lock.yml
+++ b/.github/workflows/weekly-blog-post-writer.lock.yml
@@ -1974,6 +1974,7 @@ jobs:
           GH_AW_EVALS_QUESTIONS: '[{"id":"draft-prepared","question":"Did the workflow analyze recent releases and merged pull requests and prepare a weekly blog post draft?"},{"id":"pr-created-or-explained","question":"Was a blog post pull request created when a draft was ready, or did the agent clearly explain why no pull request was needed?"}]'
           GH_AW_EVALS_MODEL: "small"
           GH_AW_EVALS_PHASE: parse
+          GITHUB_RUN_ID: ${{ github.run_id }}
         with:
           script: |
             const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
