Skip to content

fix: detach audit/audit-diff subprocess contexts from MCP gateway 60s deadline - #49061

Merged
pelikhan merged 6 commits into
mainfrom
copilot/cli-tools-test-agentic-workflows-fix-timeout-error
Jul 30, 2026
Merged

fix: detach audit/audit-diff subprocess contexts from MCP gateway 60s deadline#49061
pelikhan merged 6 commits into
mainfrom
copilot/cli-tools-test-agentic-workflows-fix-timeout-error

Conversation

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

The audit and audit-diff MCP tools reliably failed after exactly 60 seconds with context deadline exceeded because they passed the gateway's request context directly to exec.CommandContext, binding the subprocess lifetime to the gateway's per-tool RPC deadline. The logs tool had already been fixed with the correct pattern; audit and audit-diff were missing it.

Changes

  • mcp_tools_privileged.go

    • Add defaultMCPAuditTimeoutMinutes = 5 and defaultMCPAuditDiffTimeoutMinutes = 5 constants
    • Apply context.WithoutCancel + context.WithTimeout to both audit and audit-diff subprocess execution — identical to the existing logs fix
    • Goroutine watcher forwards only explicit client cancellations (context.Canceled), never the gateway's context.DeadlineExceeded
  • mcp_tools_privileged_test.go

    • Add TestAuditToolSubprocessContextIgnoresGatewayDeadline and TestAuditDiffToolSubprocessContextIgnoresGatewayDeadline — analogous to the existing TestLogsToolSubprocessContextIgnoresGatewayDeadline regression test
// Before (bound to gateway's 60s deadline)
stdout, err := runMCPExecOutput(ctx, execCmd, cmdArgs...)

// After (detached; subprocess gets its own timeout)
subCtx, subCancel := context.WithTimeout(
    context.WithoutCancel(ctx),
    time.Duration(defaultMCPAuditTimeoutMinutes)*time.Minute,
)
defer subCancel()
// goroutine forwards only context.Canceled (client disconnect), not DeadlineExceeded
stdout, err := runMCPExecOutput(subCtx, execCmd, cmdArgs...)

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.12 AIC · ⌖ 6.35 AIC · ⊞ 8.7K ·
Comment /souschef to run again

… 60s deadline

The MCP gateway imposes a per-tool RPC deadline of ~60 seconds. The logs
tool already had a fix: using context.WithoutCancel to detach the subprocess
from the gateway deadline, then applying only an explicit subprocess timeout.
The audit and audit-diff tools were missing this fix, causing every call to
fail with "context deadline exceeded" at exactly 60 seconds.

Apply the same pattern to both tools:
- Add defaultMCPAuditTimeoutMinutes (5m) and defaultMCPAuditDiffTimeoutMinutes (5m) constants
- Detach subprocess contexts from gateway deadline via context.WithoutCancel
- Forward only explicit client disconnects (context.Canceled), not deadline exceeded
- Add regression tests TestAuditToolSubprocessContextIgnoresGatewayDeadline and
  TestAuditDiffToolSubprocessContextIgnoresGatewayDeadline

Fixes #49058

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix timeout errors in logs and audit tools for agentic-workflows MCP fix: detach audit/audit-diff subprocess contexts from MCP gateway 60s deadline Jul 30, 2026
Copilot AI requested a review from pelikhan July 30, 2026 06:32
@pelikhan
pelikhan marked this pull request as ready for review July 30, 2026 06:33
Copilot AI review requested due to automatic review settings July 30, 2026 06:33
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Detaches audit subprocesses from the MCP gateway deadline while retaining bounded execution.

Changes:

  • Adds five-minute audit and audit-diff subprocess timeouts.
  • Forwards explicit cancellation while ignoring gateway deadline expiry.
  • Adds regression tests for subprocess deadlines.
Show a summary per file
File Description
pkg/cli/mcp_tools_privileged.go Adds detached, bounded subprocess contexts.
pkg/cli/mcp_tools_privileged_test.go Adds audit deadline regression tests.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

pkg/cli/mcp_tools_privileged_test.go:1053

  • As above, the deadline passed to ClientSession.CallTool is client-side and is not inherited by the server handler across the JSON-RPC transport; this immediate command therefore never tests detachment from a deadline-bearing handler context. The test would pass with context.WithTimeout(ctx, ...), so it does not guard the regression it names. Test the context-construction logic with a context that directly has the short deadline, or inject that deadline server-side and use a blocking subprocess.
	// Simulate the MCP gateway's short per-tool RPC deadline (2 s).
	gatewayCtx, gatewayCancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer gatewayCancel()

	_, _ = session.CallTool(gatewayCtx, &mcp.CallToolParams{
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment on lines +41 to +44
// defaultMCPAuditDiffTimeoutMinutes is the default subprocess timeout for the
// audit-diff tool. It downloads artifacts for all referenced runs before
// computing the diff, so it needs more headroom than a single-run audit.
defaultMCPAuditDiffTimeoutMinutes = 5
Comment thread pkg/cli/mcp_tools_privileged_test.go Outdated
Comment on lines +1009 to +1013
// Simulate the MCP gateway's short per-tool RPC deadline (2 s).
gatewayCtx, gatewayCancel := context.WithTimeout(context.Background(), 2*time.Second)
defer gatewayCancel()

_, _ = session.CallTool(gatewayCtx, &mcp.CallToolParams{

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix correctly mirrors the existing logs tool pattern — context.WithoutCancel + context.WithTimeout with a goroutine watcher that forwards only context.Canceled, not DeadlineExceeded. Tests are analogous to the existing regression test.

One non-blocking note: the comment on defaultMCPAuditDiffTimeoutMinutes says it "needs more headroom than a single-run audit" but the value is the same as defaultMCPAuditTimeoutMinutes (both 5). The comment is slightly misleading.

Overall the change is correct, well-tested, and safe to merge.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 17.9 AIC · ⌖ 6.01 AIC · ⊞ 5.3K

@github-actions

Copy link
Copy Markdown
Contributor

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🧪 Test Quality Sentinel Report

Test Quality Score: 95/100 — Excellent

Analyzed 2 test(s): 2 design, 0 implementation, 0 violation(s).

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 2, JS: 0)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (100%)
Duplicate clusters 0
Inflation No (1.19:1 ratio)
🚨 Violations 0
Test File Classification Status
TestAuditToolSubprocessContextIgnoresGatewayDeadline pkg/cli/mcp_tools_privileged_test.go:992 design_test
TestAuditDiffToolSubprocessContextIgnoresGatewayDeadline pkg/cli/mcp_tools_privileged_test.go:1032 design_test

Quality Details

Pattern Consistency: Both tests follow the established pattern from TestLogsToolSubprocessContextIgnoresGatewayDeadline, providing a consistent test suite for verifying that the three tools (logs, audit, audit-diff) correctly detach subprocess contexts from the MCP gateway's 60s deadline.

Regression Coverage: These tests directly verify the bug fix by:

  • Creating a mock execCmd that captures the context deadline
  • Simulating the MCP gateway's 2s deadline
  • Asserting that the subprocess context deadline is still ~5 minutes in the future (proving it's rooted at context.Background() not the gateway context)

Design Invariants Enforced:

  1. Subprocess contexts must be independent of gateway RPC deadlines
  2. Deadlines must be applied only at the subprocess level (audit: 5 min, audit-diff: 5 min)
  3. Client cancellations must still propagate (via the context-watcher goroutine)

Verdict

passed. 0% implementation tests (threshold: 30%). Excellent test quality: all tests verify architectural design guarantees with strong assertions and clear error-case handling.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 18.1 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 95/100. 0% implementation tests (threshold: 30%). Excellent design-focused tests with strong regression coverage for the context-deadline bug fix.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs — approving with one minor suggestion.

📋 Key Themes & Highlights

Key Themes

  • Root cause properly addressed: The fix correctly detaches subprocess contexts from the MCP gateway's RPC deadline using the proven context.WithoutCancel + context.WithTimeout pattern already established by the logs tool.
  • Regression tests present: Both new tests follow the same structure as TestLogsToolSubprocessContextIgnoresGatewayDeadline, ensuring the 60s failure mode cannot regress silently.
  • One minor doc/value inconsistency: defaultMCPAuditDiffTimeoutMinutes is documented as needing more headroom than audit but is set to the same value — see inline comment.

Positive Highlights

  • ✅ Goroutine watcher correctly forwards only context.Canceled, never context.DeadlineExceeded
  • ✅ Panic recovery in goroutines matches the existing logs pattern
  • ✅ Tests use a simulated 2 s gateway deadline to validate independence from the real 60 s deadline
  • ✅ Clear inline comments explain why each context transform is needed

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 33.9 AIC · ⌖ 4.93 AIC · ⊞ 7K
Comment /matt to run again

// defaultMCPAuditDiffTimeoutMinutes is the default subprocess timeout for the
// audit-diff tool. It downloads artifacts for all referenced runs before
// computing the diff, so it needs more headroom than a single-run audit.
defaultMCPAuditDiffTimeoutMinutes = 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] The comment says audit-diff needs more headroom than audit, but both constants are 5. Either align the comment or raise the value.

💡 Suggested fix

Either update the comment to remove the "more headroom" claim, or raise the constant to reflect the intent:

// audit-diff downloads artifacts for all referenced runs before computing the diff,
// so it needs more headroom than a single-run audit.
defaultMCPAuditDiffTimeoutMinutes = 10

Mismatched comments and values erode trust in constants over time.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (153 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/49061-detach-audit-subprocess-context-from-mcp-gateway-deadline.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-49061: Detach Audit/Audit-Diff Subprocess Contexts from MCP Gateway Deadline

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 52.2 AIC · ⌖ 7.11 AIC · ⊞ 9.6K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: fix is correct but the underlying timeout scaling is incomplete

The core fix (detaching subprocess context from the gateway RPC deadline via WithoutCancel+WithTimeout, forwarding only client cancellations) correctly mirrors the proven logs tool pattern and is well tested with regression tests for both audit and audit-diff.

💡 Themes
  • Flat timeout vs. scaling by request size (high): unlike logs, which scales its timeout with the number of runs/workflows requested, audit/audit-diff use a flat 5-minute constant even though both accept multiple run IDs. Large multi-run audits/diffs can still hit context deadline exceeded, just at 300s instead of 60s.
  • Duplicated boilerplate (low): the WithoutCancel/WithTimeout + watcher-goroutine pattern is now copy-pasted three times (logs, audit, audit-diff); worth extracting to a shared helper to avoid future divergence.

Neither issue is a blocking correctness bug in this diff, so filing as COMMENT rather than REQUEST_CHANGES, but the timeout-scaling gap should be addressed promptly since it can reproduce the original bug report under load.

🔎 Code quality review by PR Code Quality Reviewer · auto · 81.1 AIC · ⌖ 4.72 AIC · ⊞ 7.8K
Comment /review to run again

// tool. Auditing a single run typically takes 5–30 s, but large runs with many
// artifact sets can take longer. 5 minutes gives ample headroom while still
// bounding the subprocess lifetime.
defaultMCPAuditTimeoutMinutes = 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 5-minute timeout regardless of how many runs are audited/diffed — this can reintroduce the same premature-timeout bug the PR is fixing, just at a different threshold.

💡 Details

audit accepts run_ids_or_urls (multiple runs) and audit-diff accepts compare_run_ids (multiple comparison runs), but both use a flat defaultMCPAuditTimeoutMinutes = 5 / defaultMCPAuditDiffTimeoutMinutes = 5 constant. The sibling logs tool in this same file already solves this by scaling its timeout with effectiveMCPLogsToolTimeoutMinutes(...) based on request size. Auditing/diffing many runs — each requiring its own artifact download — can legitimately exceed 5 minutes, causing the exact context deadline exceeded symptom this PR is fixing, just moved from 60s to 300s.

Suggested fix: scale the timeout by len(runItems) / len(args.CompareRunIDs) similar to the logs tool per-count scaling, or expose a configurable timeout parameter like logs does.

Comment thread pkg/cli/mcp_tools_privileged.go Outdated
// disconnect) so the subprocess is cleaned up promptly when the caller goes away.
subCtx, subCancel := context.WithTimeout(
context.WithoutCancel(ctx),
time.Duration(defaultMCPAuditTimeoutMinutes)*time.Minute,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context-detach-and-cancellation-watcher block is now copy-pasted verbatim across three tool handlers (logs, audit, audit-diff).

💡 Details

Each of the three call sites duplicates the same ~15 lines: context.WithTimeout(context.WithoutCancel(ctx), ...) plus a panic-recovering goroutine that forwards only context.Canceled. Any future correction to this cancellation-forwarding semantics (e.g. a subtle bug in the Canceled-only forwarding, or a need to also propagate a signal shutdown) must now be manually replicated in three places, and it is easy for the copies to silently diverge over time.

Suggested fix: extract a helper, e.g. func withDetachedTimeout(ctx context.Context, timeout time.Duration, logPrefix string) (context.Context, context.CancelFunc), that encapsulates the WithoutCancel/WithTimeout + watcher goroutine, and call it from all three tools.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the latest PR feedback, and rerun checks once the branch is current.

Reviewer follow-up:

  • Design Decision Gate requests an ADR for the core pkg/ logic change.

Run: https://github.com/github/gh-aw/actions/runs/30520508562

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.12 AIC · ⌖ 6.35 AIC · ⊞ 8.7K ·
Comment /souschef to run again

@github-actions

Copy link
Copy Markdown
Contributor

Triage Summary

  • Category: bug
  • Risk: medium
  • Priority Score: 62/100 (impact 30 + urgency 20 + quality 12)
  • Recommended Action: fast_track

Fixes reliable 60s timeout failure in audit/audit-diff MCP tools by detaching subprocess context from gateway deadline. Not draft.

Generated by 🔧 PR Triage Agent · auto · 54.9 AIC · ⌖ 5.64 AIC · ⊞ 7.9K ·

…t tests

- Fix misleading comment on defaultMCPAuditDiffTimeoutMinutes (claimed
  more headroom than single-run audit; both values are equal at 5 min)
- Extract newMCPSubprocessContext helper encapsulating the context.WithoutCancel
  + context.WithTimeout pattern with the cancellation-forwarding goroutine
- Refactor audit and audit-diff handlers to use the helper (DRY)
- Rewrite TestAuditToolSubprocessContextIgnoresGatewayDeadline and
  TestAuditDiffToolSubprocessContextIgnoresGatewayDeadline to call
  newMCPSubprocessContext directly with a deadline-bearing context,
  so the test actually exercises the detachment logic (the previous
  implementation went through session.CallTool which does not propagate
  the client's context deadline to the server handler)

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the latest PR feedback, and rerun checks once the branch is current.

Reviewer follow-up:...

Addressed in the latest commit. Here's what changed:

Comment fix (lines 41–44): Removed the incorrect claim that audit-diff needs "more headroom" — both constants are 5 minutes, so the comment now accurately says "5 minutes gives ample headroom for the artifact-download and diff steps."

Test fix (lines 1009–1013 and 1049): The reviewer was right — session.CallTool(gatewayCtx, ...) doesn't propagate the client's context deadline to the server handler. The MCP in-memory transport wraps the connection context in a notDone{} that strips all deadlines, so the captured deadline in the mock was always ~5 minutes regardless of whether context.WithoutCancel was used or not.

To fix this properly, I extracted the detachment logic into a newMCPSubprocessContext(ctx, timeout, toolName) helper that consolidates the context.WithoutCancel + context.WithTimeout + forwarding goroutine pattern used by both audit and audit-diff handlers. The tests now call that helper directly with a 2-second gateway deadline:

gatewayCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
subCtx, subCancel := newMCPSubprocessContext(gatewayCtx, 5*time.Minute, "audit")
deadline, _ := subCtx.Deadline()
// asserts deadline is ~5 minutes from now, NOT 2 seconds

I verified the regression guard works: temporarily removing context.WithoutCancel from the helper causes both tests to fail as expected.

ADR: The draft ADR (docs/adr/49061-detach-audit-subprocess-context-from-mcp-gateway-deadline.md) was already committed in an earlier commit.

Copilot AI requested a review from gh-aw-bot July 30, 2026 07:18
@pelikhan
pelikhan enabled auto-merge (squash) July 30, 2026 07:44
@pelikhan
pelikhan merged commit 61519bb into main Jul 30, 2026
28 checks passed
@pelikhan
pelikhan deleted the copilot/cli-tools-test-agentic-workflows-fix-timeout-error branch July 30, 2026 07:46
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[cli-tools-test] agentic-workflows MCP: logs and audit tools always time out with context deadline exceeded after 60s

4 participants