Skip to content

Scale MCP logs timeout for larger fetch windows#42295

Merged
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-raise-logs-timeout
Jun 29, 2026
Merged

Scale MCP logs timeout for larger fetch windows#42295
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-raise-logs-timeout

Conversation

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

The MCP logs tool was still defaulting to a 1-minute timeout, which caused fleet-wide queries to hit the 60s server deadline once the requested window grew beyond ~40 runs. This change raises the effective default budget for larger requests without changing explicit user-provided timeouts or the existing continuation behavior.

  • MCP logs timeout behavior

    • Replace the fixed 1-minute fallback with a count-aware default timeout.
    • Keep explicit timeout values authoritative.
    • Preserve the existing CLI/orchestrator continuation flow; this only changes the implicit MCP-side budget selection.
  • Default timeout tiers

    • Up to 40 runs: 1 minute
    • 41–80 runs: 2 minutes
    • Default 100-run window: 3 minutes
  • Schema/default alignment

    • Update the MCP tool schema default for timeout to match the new count-scaled behavior.
    • Clarify the tool description so the timeout tiers are visible to callers.
  • Targeted test updates

    • Add focused coverage for the new implicit-timeout calculation.
    • Update the MCP default-schema test to assert the real logs-tool defaults.
func effectiveMCPLogsToolTimeoutMinutes(requestedTimeout, count int) int {
	if requestedTimeout > 0 {
		return requestedTimeout
	}
	return defaultMCPLogsToolTimeoutMinutesForCount(count)
}

Generated by 👨‍🍳 PR Sous Chef · 193.2 AIC · ⌖ 8.61 AIC · ⊞ 6.7K ·

Copilot AI and others added 2 commits June 29, 2026 16:59
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] Raise logs tool timeout and add pagination for over 40 runs Scale MCP logs timeout for larger fetch windows Jun 29, 2026
Copilot AI requested a review from pelikhan June 29, 2026 17:02
@pelikhan pelikhan marked this pull request as ready for review June 29, 2026 17:06
Copilot AI review requested due to automatic review settings June 29, 2026 17:06

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

This PR updates the MCP logs tool to choose a larger implicit timeout for larger log fetch windows, reducing default 60s deadline hits on fleet-wide queries while keeping explicit user-provided timeouts authoritative.

Changes:

  • Introduces a count-aware default timeout calculation for MCP logs requests and uses it when timeout is omitted.
  • Updates the MCP tool schema defaults/description to reflect the new implicit timeout behavior.
  • Adds/updates unit tests to cover the new implicit-timeout logic and schema defaults.
Show a summary per file
File Description
pkg/cli/mcp_tools_privileged.go Adds count-based implicit timeout selection and updates the MCP logs tool schema defaults/description.
pkg/cli/mcp_server_defaults_test.go Aligns schema-default assertions with the updated MCP logs tool defaults.
pkg/cli/logs_timeout_test.go Adds focused unit coverage for the new implicit MCP logs timeout calculation.

Review details

Tip

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

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines 204 to 209
cmdArgs = appendRepoFlagFromEnv(cmdArgs)

// Set timeout to 1 minute for MCP server if not explicitly specified
timeoutValue := args.Timeout
if timeoutValue == 0 {
timeoutValue = 1
}
// 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, args.Count)
cmdArgs = append(cmdArgs, "--timeout", strconv.Itoa(timeoutValue))
Comment thread pkg/cli/mcp_tools_privileged.go Outdated
AfterRunID int64 `json:"after_run_id,omitempty" jsonschema:"Filter runs with database ID after this value (exclusive)"`
BeforeRunID int64 `json:"before_run_id,omitempty" jsonschema:"Filter runs with database ID before this value (exclusive)"`
Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in minutes to spend downloading logs (default: 1 for MCP server)"`
Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in minutes to spend downloading logs (default: auto-scales with count in the MCP server: 1 minute for up to 40 runs, 2 minutes for 41-80 runs, and 3 minutes for the default 100-run window)"`
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR #42295 does not have the 'implementation' label and has only 84 new lines of code in business logic directories (threshold: 100). ADR gate skipped.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill to bring this PR to a mergeable state. The branch has been queued for a base-branch update — once updated, rerun any failing checks and verify the PR is ready to merge.

Generated by 👨‍🍳 PR Sous Chef · 193.2 AIC · ⌖ 8.61 AIC · ⊞ 6.7K ·

@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: Scale MCP logs timeout for larger fetch windows

The change is correct and well-structured. The math checks out across all documented tiers, the previous tautological test is replaced with a proper table-driven suite, and the schema/test constants are now derived from the source-of-truth rather than hardcoded magic numbers.

Two non-blocking suggestions are filed as inline comments:

  1. Missing tier-boundary test — the 80 -> 81 boundary (2 to 3 min) is not covered; only the 40 -> 41 boundary is tested.
  2. Schema default vs. runtime default — the elicitation default for timeout (3 min) reflects the 100-run tier, but callers who pass a smaller count will get a lower effective timeout at runtime. A short clarifying comment would help.

Neither is blocking. The core logic, constants, and formula are correct.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 36.6 AIC · ⌖ 6.63 AIC · ⊞ 4.9K

requestedTimeout: 0,
count: 0,
want: 3,
},

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 test suite covers the 40 → 41 boundary (1 → 2 min) but misses the 80 → 81 boundary (2 → 3 min). Consider adding test cases for count=80 (expect 2 min) and count=81 (expect 3 min) to fully exercise both tier transitions and guard against accidental formula changes.

@copilot please address this.

mcpLog.Printf("Failed to add default for count: %v", err)
}
if err := AddSchemaDefault(logsSchema, "timeout", 1); err != nil {
if err := AddSchemaDefault(logsSchema, "timeout", defaultMCPLogsToolTimeoutMinutesForCount(defaultMCPLogsToolCount)); err != nil {

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 schema default for timeout is pinned to the 100-run tier (3 min). A caller that provides an explicit count=10 but no timeout will get a 1-minute effective timeout at runtime, despite the schema hint suggesting 3. The jsonschema tag description on the field already documents the tiers, but the schema default could mislead callers who assume it applies to any count value.

Consider adding a small comment here like:

// Schema default corresponds to defaultMCPLogsToolCount; actual runtime value
// scales down for smaller count values — see defaultMCPLogsToolTimeoutMinutesForCount.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100 — Excellent

Analyzed 2 test(s): 2 design, 0 implementation, 0 guideline violation(s). Both tests directly verify behavioral contracts for the new count-aware timeout-scaling logic.

📊 Metrics & Test Classification (2 tests analyzed)
Metric Value
New/modified tests analyzed 2
✅ Design tests (behavioral contracts) 2 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (50%)
Duplicate test clusters 0
Test inflation detected No (ratio 1.53 — under 2:1)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
TestEffectiveMCPLogsToolTimeoutMinutes (NEW — replaces no-op TestMCPServerDefaultTimeout) pkg/cli/logs_timeout_test.go:115 ✅ Design
TestMCPToolElicitationDefaults (logs-defaults section, MODIFIED) pkg/cli/mcp_server_defaults_test.go:66 ✅ Design

Go: 2 (*_test.go); JavaScript: 0. Build tags: both files carry //go:build !integration on line 1 ✅. No mock libraries detected ✅.

Score breakdown

Component Earned Max
Behavioral coverage (2/2 design tests) 40 40
Edge/error case coverage (1/2 — TestEffectiveMCPLogsToolTimeoutMinutes covers count=0, count=41 boundary, explicit-override path; TestMCPToolElicitationDefaults changes add no new error paths) 15 30
Low duplication (0 duplicate clusters) 20 20
Proportional growth (test +54 / prod +30 = 1.8:1) 10 10
Total 85 100
🔍 Per-test analysis

TestEffectiveMCPLogsToolTimeoutMinutes (pkg/cli/logs_timeout_test.go:115) — New, replaces TestMCPServerDefaultTimeout

The old test was a compile-time no-op: it set a local variable then immediately checked its own set value — it could never fail. The replacement is a table-driven test with 5 rows that exercise the real effectiveMCPLogsToolTimeoutMinutes() function:

Row requestedTimeout count want Notes
explicit timeout is preserved 5 100 5 Behavioral contract: explicit beats auto-scale
small fetch window keeps one minute default 0 40 1 At-threshold boundary
fetch window above forty runs gets two minutes 0 41 2 Just-above-threshold boundary
default hundred run window gets three minutes 0 100 3 Default-count case
unspecified count falls back to default window size 0 0 3 Edge case: zero count

Assertions use t.Errorf with full parameter context. ✅ No testify/mock; boundary + edge cases present.

TestMCPToolElicitationDefaults (pkg/cli/mcp_server_defaults_test.go:66) — Modified (logs-tool defaults section)

Two changes: replaces hardcoded magic numbers (100, 50) with the production constants defaultMCPLogsToolCount and defaultMCPLogsToolTimeoutMinutesForCount(defaultMCPLogsToolCount). This removes a dangerous drift risk where the test and the production code could silently disagree on the default values. The assertions now pin the relationship between the schema default and the production function output, rather than a fixed integer. Setup uses t.Fatalf for error paths.

Verdict

Check passed. 0% implementation tests (threshold: 30%), no coding-guideline violations. The new TestEffectiveMCPLogsToolTimeoutMinutes replaces a functionally inert predecessor with genuine behavioral coverage across all documented timeout tiers and boundary conditions.

References: §28389435456

🧪 Test quality analysis by Test Quality Sentinel · 52.1 AIC · ⌖ 17.4 AIC · ⊞ 8.1K ·
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: 85/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%).

@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 /tdd and /grill-with-docs — approving with two minor suggestions.

📋 Key Themes & Highlights

Key Themes

  • Test boundary gap: Tier 1→2 (count 40/41) is tested; tier 2→3 (count 80/81) is not — a future constant change could silently regress.
  • Schema description drift: Timeout struct tag hardcodes tier numbers that should mirror mcpLogsRunsPerDefaultTimeoutMinute.

Positive Highlights

  • ✅ Correct ceiling-division formula — verified manually against all four tier boundaries.
  • ✅ Replaces a tautological mock test (timeout == 50 → want 50) with real table-driven tests that exercise the live function.
  • ✅ Clean two-function split: defaultMCPLogsToolTimeoutMinutesForCount for the policy and effectiveMCPLogsToolTimeoutMinutes for the override logic.
  • ✅ Named constants for every magic number; no bare literals left in the logic path.
  • mcp_server_defaults_test.go is now self-consistent — schema defaults reference the same constants as the implementation.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 62.1 AIC · ⌖ 7.43 AIC · ⊞ 6.6K
Comment /matt to run again

count: 0,
want: 3,
},
}

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 tier 1→2 boundary (count=40/41) is tested but the tier 2→3 boundary (count=80/81) is not. A future off-by-one in the ceiling-division formula would only be caught at the tested boundaries.

💡 Two additional cases to add before line 152
{
	name:             "top of tier 2 keeps two minutes",
	requestedTimeout: 0,
	count:            80,
	want:             2,
},
{
	name:             "bottom of tier 3 gets three minutes",
	requestedTimeout: 0,
	count:            81,
	want:             3,
},

These pin both sides of the second tier boundary, matching the depth of coverage already present for the first boundary (40/41).

@copilot please address this.

Comment thread pkg/cli/mcp_tools_privileged.go Outdated
AfterRunID int64 `json:"after_run_id,omitempty" jsonschema:"Filter runs with database ID after this value (exclusive)"`
BeforeRunID int64 `json:"before_run_id,omitempty" jsonschema:"Filter runs with database ID before this value (exclusive)"`
Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in minutes to spend downloading logs (default: 1 for MCP server)"`
Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in minutes to spend downloading logs (default: auto-scales with count in the MCP server: 1 minute for up to 40 runs, 2 minutes for 41-80 runs, and 3 minutes for the default 100-run window)"`

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.

[/grill-with-docs] The jsonschema description hardcodes the tier boundaries ("1 minute for up to 40 runs, 2 minutes for 41-80 runs, and 3 minutes for the default 100-run window"), but the real boundaries are derived from mcpLogsRunsPerDefaultTimeoutMinute. If that constant changes the description will silently drift, misleading MCP callers.

💡 Suggestion

Add a short comment above the field pointing readers at the authoritative constants, or use a generate step to embed the computed strings. At minimum, note in a comment that the inline numbers shadow mcpLogsRunsPerDefaultTimeoutMinute:

// Tier thresholds mirror mcpLogsRunsPerDefaultTimeoutMinute; update this
// string if that constant changes.
Timeout int `json:"timeout,omitempty" jsonschema:"..."`

This keeps the description honest without requiring a runtime approach.

@copilot please address this.

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

Two correctness issues need fixing before merge — plus two supporting issues already flagged in earlier comments.

### Blocking issues (this review)

1. timeout: 0 cannot reach the CLI's "unlimited" mode (line 71, effectiveMCPLogsToolTimeoutMinutes)

The guard if requestedTimeout > 0 silently treats an explicit 0 identically to an omitted value, so an MCP caller who intends --timeout 0 (no timeout, CLI-documented contract) always gets an auto-computed budget. The PR description says "Keep explicit timeout values authoritative" — this code doesn't deliver that. Either pass 0 through as-is, or explicitly document and enforce that unlimited mode is intentionally blocked.

2. No upper bound on computed timeout (line 62, defaultMCPLogsToolTimeoutMinutesForCount)

count=1000 → 25-minute timeout; count=10000 → ~4-hour hang. The schema count field has no maximum constraint, making the handler susceptible to over-large inputs. A hard cap (e.g., 10 minutes) should be applied and exposed in the schema.

### Supporting issues (also flagged in this review)
  • Test "unspecified count falls back to default window size" encodes the count=0 / 10-run inconsistency (identified by a prior comment) as expected behavior, making it harder to fix.
  • Stale "seconds" in mcp_server_defaults_test.go:59 — test fixture out of sync with the real logsArgs.Timeout field which now says "minutes".

Warning

Firewall blocked 1 domain

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

  • patchdiff.githubusercontent.com

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

network:
  allowed:
    - defaults
    - "patchdiff.githubusercontent.com"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer · 95.8 AIC · ⌖ 9.09 AIC · ⊞ 5.2K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/mcp_tools_privileged.go:71

timeout: 0 cannot request unlimited execution — contradicts the PR's stated design goal: if requestedTimeout &gt; 0 silently treats an explicit 0 the same as an omitted value, so an MCP caller who passes timeout: 0 to opt into unlimited mode receives an auto-computed budget instead.

<details>
<summary>💡 Details and suggested fix</summary>

The CLI contract is explicit: --timeout 0 means "no timeout" (logs_command.go flag: &quot;Download timeout in minutes (0 = no timeout)&quot;). The PR …

pkg/cli/mcp_tools_privileged.go:62

No upper bound on computed timeout — a large count can produce arbitrarily long hangs: count=1000 yields a 25-minute timeout; there is no schema maximum on count and no cap in this function.

<details>
<summary>💡 Details and suggested fix</summary>

The formula (count + 39) / 40 scales linearly without a ceiling:

count computed timeout
120 3 min
121 4 min
200 5 min
1000 25 min …
pkg/cli/logs_timeout_test.go:150

This test case encodes a known bug as expected behavior: the test asserts count=0 → 3-minute timeout because defaultMCPLogsToolTimeoutMinutesForCount substitutes defaultMCPLogsToolCount=100. But when args.Count==0, the handler doesn't pass -c to the CLI, which then fetches only 10 runs (its own default). The timeout is over-provisioned by 3× and the test name implies this is correct.

<details>
<summary>💡 Why this matters</summary>

An existing review comment on this PR already i…

pkg/cli/mcp_server_defaults_test.go:59

Stale unit descriptor in test fixture: this local logsArgs struct still says &quot;Maximum time in seconds&quot; while the real logsArgs.Timeout field (changed in this PR) says &quot;minutes&quot;. Because the test redefines the struct locally instead of importing the real type, schema-documentation drift goes undetected.

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

Change the local field tag to match production:

Timeout int `json:&quot;timeout,omitempty&quot; jsonschema:&quot;Maximum time in minutes to spen…

</details>

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

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill to bring this PR to a mergeable state. The branch has been queued for a base-branch update — once updated, rerun any failing checks and verify the PR is r...

Addressed in 75db04a.

I ran the pr-finisher workflow for this branch: applied/validated the remaining timeout-review fixes, ran local validation (make fmt, make lint, make test-unit, make test), and pushed the update. Current PR head is updated; CI for this new head is not yet green in this agent context and needs maintainer re-trigger to fully verify merge readiness.

Copilot AI requested a review from gh-aw-bot June 29, 2026 17:54
@pelikhan pelikhan merged commit 8bd52f7 into main Jun 29, 2026
29 checks passed
@pelikhan pelikhan deleted the copilot/deep-report-raise-logs-timeout branch June 29, 2026 18:14
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.

[deep-report] [quick-win] Raise logs tool timeout / add pagination above ~40 runs

4 participants