Skip to content

Fix logs tool: context deadline exceeded without workflow_name filter#46554

Merged
pelikhan merged 6 commits into
mainfrom
copilot/cli-tools-test-logs-timeout-fix
Jul 19, 2026
Merged

Fix logs tool: context deadline exceeded without workflow_name filter#46554
pelikhan merged 6 commits into
mainfrom
copilot/cli-tools-test-logs-timeout-fix

Conversation

Copilot AI commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

agenticworkflows logs --count 3 without --workflow_name always timed out (60s MCP gateway limit) due to three compounding issues in the no-workflow code path.

Root causes & fixes

  • BatchSizeForAllWorkflows 250 → 100 (logs_models.go)
    gh run list --limit 250 pages as 3 sequential GitHub API calls (max 100/page). For repos with 250+ workflows, that's 45–60s+ just for the list step. Reducing to 100 means 1 call per batch.

  • Context not propagated to gh run list (logs_orchestrator_download.go)
    fetchWorkflowRunBatch was calling the API with Context: nil (→ context.Background()), so the subprocess had no deadline. Added ctx context.Context parameter threaded from runtime.activeCtx. Also added graceful handling: DeadlineExceeded returns partial results (treated as soft timeout), Canceled propagates with partial results.

  • Static timeout schema default bypassed per-request computation (mcp_tools_privileged.go)
    AddSchemaDefault(logsSchema, "timeout", 3) caused the go-sdk to fill args.Timeout = 3 before the handler ran, making effectiveMCPLogsToolTimeoutMinutes always hit the "explicit timeout" branch. Removed the static default so the runtime computes the correct value from (requestedTimeout, count, workflowName).

  • 5-minute minimum for no-workflow queries
    Added defaultMCPLogsMinTimeoutMinutesAllWorkflows = 5. effectiveMCPLogsToolTimeoutMinutes now accepts workflowName string and applies this floor when no workflow is scoped, giving adequate headroom for fleet-wide queries.

Copilot AI and others added 2 commits July 19, 2026 07:48
Three root-cause fixes:
1. Reduce BatchSizeForAllWorkflows from 250 to 100 (1 API call vs 3)
2. Propagate context to fetchWorkflowRunBatch so gh run list respects deadline
3. Apply 5-minute minimum timeout for no-workflow-filter requests
4. Remove static schema default for timeout to let runtime compute it per-request

Closes #46538

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix timeout issue in logs tool when no workflow_name filter is applied Fix logs tool: context deadline exceeded without workflow_name filter Jul 19, 2026
Copilot AI requested a review from pelikhan July 19, 2026 07:50
@pelikhan
pelikhan marked this pull request as ready for review July 19, 2026 08:16
Copilot AI review requested due to automatic review settings July 19, 2026 08:16
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

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

Fixes fleet-wide logs queries that time out while listing workflow runs.

Changes:

  • Limits unfiltered batches to one GitHub API page.
  • Propagates cancellation contexts into gh run list.
  • Computes MCP timeouts dynamically and expands coverage.
Show a summary per file
File Description
pkg/cli/mcp_tools_privileged.go Computes workflow-aware timeouts.
pkg/cli/mcp_tools_privileged_test.go Tests generated command arguments.
pkg/cli/mcp_server_defaults_test.go Verifies timeout has no schema default.
pkg/cli/logs_timeout_test.go Tests timeout scaling.
pkg/cli/logs_orchestrator_download.go Propagates context and handles cancellation.
pkg/cli/logs_models.go Reduces fleet-wide batch size.

Review details

Tip

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

  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Medium

// Without a workflow filter the GitHub API must scan all workflow runs, which
// is substantially slower for large repositories. Apply a higher minimum so
// the tool is less likely to exhaust the MCP gateway's per-tool timeout.
return max(defaultMCPLogsMinTimeoutMinutesAllWorkflows, base)
Comment on lines +196 to +206
if errors.Is(err, context.DeadlineExceeded) {
timeoutReached = true
break
}
// Context cancelled (e.g. user signal or outer gateway timeout): propagate
// with partial results. We intentionally leave timeoutReached=false because
// this is an external cancellation, not the internal --timeout deadline firing.
// Callers can inspect the returned error to distinguish the two cases.
if errors.Is(err, context.Canceled) {
return processedRuns, timeoutReached, countLimitReached, err
}

@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.

Review: Fix logs tool timeout

The four-pronged fix is well-targeted and the changes are correct.

BatchSizeForAllWorkflows 250 → 100 — aligns with the GitHub API per-page cap (100), eliminating extra round-trips that caused timeouts.

Context propagation — threading ctx through to listWorkflowRunsWithPagination is the right fix. The soft-timeout / hard-cancel distinction (DeadlineExceeded breaks with partial results; Canceled propagates the error) is semantically correct and consistent with the existing caller.

Removing the static schema default for timeout — sound reasoning: the go-sdk fills in schema defaults before the handler runs, so a static value would always win over the runtime computation. Removing it lets args.Timeout == 0 trigger the runtime floor correctly.

5-minute floor for all-workflow queries — conservative and appropriate. Test coverage in logs_timeout_test.go and mcp_server_defaults_test.go is thorough.

No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 17.2 AIC · ⌖ 4.38 AIC · ⊞ 5K

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100 — Excellent

Analyzed 3 test functions: all 3 are design tests covering behavioral contracts. No guideline violations.

📊 Metrics (3 test functions)
Metric Value
Analyzed 3 (Go: 3, JS: 0)
✅ Design tests 3 (100%)
⚠️ Implementation tests 0 (0%)
Edge/error coverage Yes — boundaries at 40/41/80/81 runs, all-workflow floor logic
Duplicate clusters 0
Test inflation Marginal (logs_timeout_test: 2.14:1, justified by new scenarios)
🚨 Violations 0
Test File Classification Coverage
TestEffectiveMCPLogsToolTimeoutMinutes (table-driven, 10 cases) logs_timeout_test.go design_test Explicit timeout preservation (2 cases), count-based scaling tiers (5 cases), all-workflow floor (3 cases)
TestMCPToolElicitationDefaults (sub-test) mcp_server_defaults_test.go design_test Verifies NO static timeout default (runtime-computed)
TestLogsToolUsesEffectiveCountForTimeoutScaling (sub-test) mcp_tools_privileged_test.go design_test Verifies timeout passed to subprocess computed correctly

Verdict

Passed. 0% implementation tests (threshold: 30%). All tests verify design contracts:

  • Explicit timeout preservation across all modes
  • Count-based scaling (1/2/3 min tiers) for single-workflow queries
  • 5-minute floor for all-workflow queries (no workflow_name filter)
  • Schema contract that runtime computes timeout, not SDK defaults
  • Subprocess invocation correctly applies timeout

The test expansion is justified: new workflow_name parameter required comprehensive coverage of both named-workflow and all-workflow behavior paths.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 12.6 AIC · ⌖ 8.86 AIC · ⊞ 7K ·
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: 90/100. All 3 test functions verify behavioral design contracts with comprehensive coverage of timeout scaling logic and edge cases. 0% implementation tests (threshold: 30%). No guideline violations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

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

📄 Draft ADR committed: docs/adr/46554-dynamic-timeout-computation-for-mcp-logs-tool.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-46554: Dynamic Timeout Computation for the MCP Logs Tool

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

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

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 🏗️ · 44.7 AIC · ⌖ 12.9 AIC · ⊞ 8.5K ·
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.

Skills-Based Review

Applied /diagnosing-bugs and /tdd — two issues found, requesting attention.

Key Themes

  • Partial-result contract is unimplemented (see inline comment on logs_orchestrator_download.go): the Canceled branch documents it returns partial results, but the caller discards them on any non-nil error.
  • Magic number in test (see inline comment on logs_timeout_test.go): one case hardcodes want: 7 derived from ceil(250/40) — brittle if the constant changes.

Positive Highlights

  • Root-cause analysis is clear and thorough.
  • Removing the static timeout schema default is the right call; the inline comment is well-placed.
  • Good test matrix: explicit, named-workflow, and all-workflow cases all covered.
  • BatchSizeForAllWorkflows = 100 correctly matches the GitHub API per_page maximum.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 36 AIC · ⌖ 4.62 AIC · ⊞ 6.7K
Comment /matt to run again

Comment thread pkg/cli/logs_orchestrator_download.go Outdated
// this is an external cancellation, not the internal --timeout deadline firing.
// Callers can inspect the returned error to distinguish the two cases.
if errors.Is(err, context.Canceled) {
return processedRuns, timeoutReached, countLimitReached, err

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 "propagate with partial results" but the caller discards them — the partial processedRuns are lost when this error is returned.

💡 Details

collectProcessedWorkflowRuns returns (processedRuns, timeoutReached, countLimitReached, err), but its caller in logs_orchestrator.go does:

processedRuns, timeoutReached, countLimitReached, err := collectProcessedWorkflowRuns(runtime, opts)
if err != nil {
    return err   // partial runs discarded here
}

So on external cancellation (context.Canceled) the already-fetched runs are silently dropped. Either:

  1. Return (nil, false, false, err) here (like the generic path below) and document that partial results are not surfaced for Canceled, OR
  2. Thread partial-result surfacing through the caller so they are rendered before the error is returned.

The current code misleads future readers.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7aef8a5. The context.Canceled branch now returns (nil, false, false, err) consistent with the generic error path. The misleading "propagate with partial results" comment has been removed. Additionally, listWorkflowRunsWithPagination now surfaces ctx.Err() directly when exec.CommandContext kills the subprocess, so errors.Is(err, context.Canceled) and errors.Is(err, context.DeadlineExceeded) in the caller work as expected.

t.Errorf("effectiveMCPLogsToolTimeoutMinutes(%d, %d, %q) = %d, want %d", tt.requestedTimeout, tt.count, tt.workflowName, got, tt.want)
}
})
}

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.

[/tdd] The "very large count exceeds all-workflow minimum" test hardcodes want: 7 with a comment // ceil(250/40) = 7 — tying the test to implementation arithmetic rather than the exported constant.

💡 Suggestion

If mcpLogsRunsPerDefaultTimeoutMinute or the ceiling formula ever changes, this test will silently mismatch without the reader noticing. Consider:

want: effectiveMCPLogsToolTimeoutMinutes(0, 250, ""),

or at minimum:

want: (250 + mcpLogsRunsPerDefaultTimeoutMinute - 1) / mcpLogsRunsPerDefaultTimeoutMinute,

This keeps the test honest without coupling it to a magic number.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7aef8a5. Replaced the hardcoded 7 with (250 + mcpLogsRunsPerDefaultTimeoutMinute - 1) / mcpLogsRunsPerDefaultTimeoutMinute so the expected value stays in sync with the constant automatically.

@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.

Review: Fix logs tool context deadline / timeout

The three-part fix (batch size, context propagation, dynamic timeout floor) is well-reasoned and the test coverage is thorough. One non-blocking inconsistency worth addressing: the context.Canceled path does an early return where the context.DeadlineExceeded path uses break — see the inline comment.

🔎 Code quality review by PR Code Quality Reviewer · 36 AIC · ⌖ 4.43 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/logs_orchestrator_download.go:43

context.Canceled early return bypasses post-loop diagnostics: the timeout/iteration log statements after the loop are silently skipped, making it hard to diagnose why a run stopped.

<details>
<summary>💡 Suggested fix</summary>

The context.DeadlineExceeded branch uses break and falls through to post-loop logging. For consistency and debuggability, the Canceled path should do the same:

// Instead of early return, record the error and break
var cancelledErr error
if errors.Is</details>

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

… misleading partial-results-for-Canceled; tie test want to constant

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
pelikhan merged commit 0b22462 into main Jul 19, 2026
23 checks passed
@pelikhan
pelikhan deleted the copilot/cli-tools-test-logs-timeout-fix branch July 19, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[cli-tools-test] logs tool: context deadline exceeded when querying without workflow_name filter

3 participants