Skip to content

Share API rate-limit state across multi-target logs downloads - #60531

Merged
pelikhan merged 7 commits into
mainfrom
copilot/update-logs-command-rate-limit
Sep 13, 2026
Merged

pelikhan merged 7 commits into
mainfrom
copilot/update-logs-command-rate-limit

Conversation

Copilot AI commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Multi-target gh aw logs downloads independently handled GitHub API rate limits, leaving queued work blocked until reset. This change coordinates rate-limit enforcement across the command and stops outstanding work once the shared ceiling is reached.

Changes

  • Shared rate-limit state

    • Serialize checks across concurrent targets.
    • Reuse the terminal “limit reached” state without additional API calls.
  • Prompt cancellation

    • Cancel queued and in-flight target work when the ceiling is reached.
    • Avoid waiting for reset during multi-target downloads.
  • Partial-result recovery

    • Preserve continuation parameters for interrupted targets.
    • Keep existing wait-for-reset behavior for single-target downloads.
  • CLI guidance

    • Clarify the different single-target and multi-target behaviors for --max-github-api-rate-limit.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Share API rate-limit state across logs targets Share API rate-limit state across multi-target logs downloads Sep 13, 2026
Copilot AI requested a review from pelikhan September 13, 2026 01:12
@pelikhan
pelikhan marked this pull request as ready for review September 13, 2026 01:13
Copilot AI balanced review requested due to automatic review settings September 13, 2026 01:13

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.

🟡 Changes recommended

Rate limits reached before or during artifact processing can lose or skip continuation data.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Coordinates GitHub API rate-limit handling across concurrent multi-target log downloads.

Changes:

  • Adds shared rate-limit state and cancellation.
  • Generates continuations for interrupted targets.
  • Updates CLI guidance and tests.
File summaries
File Description
pkg/cli/mcp_tools_privileged.go Updates MCP option guidance.
pkg/cli/logs_run_processor.go Applies shared checks to artifact downloads.
pkg/cli/logs_rate_limit.go Implements shared terminal rate-limit state.
pkg/cli/logs_rate_limit_test.go Tests stopping and state reuse.
pkg/cli/logs_orchestrator.go Builds rate-limit continuations.
pkg/cli/logs_orchestrator_unit_test.go Updates continuation tests.
pkg/cli/logs_orchestrator_types.go Adds shared state to options.
pkg/cli/logs_orchestrator_download.go Propagates cancellation through collection.
pkg/cli/logs_multi.go Coordinates target cancellation and continuations.
pkg/cli/logs_multi_test.go Tests queued-target cancellation.
pkg/cli/logs_command.go Clarifies CLI behavior.
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Balanced (auto)

Note

Copilot is running an experiment and ran this review at Balanced.


💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/cli/logs_multi.go
return logsTargetResult{target: target, result: countLimitedLogsTargetResult(targetOpts)}
}
if shared.rateLimitState.isReached() {
return logsTargetResult{target: target, result: rateLimitedLogsTargetResult(targetOpts), err: errLogsAPIRateLimitReached}
maxConcurrentDownloads: opts.maxConcurrentDownloads,
storageLimit: runtime.storageLimit,
maxGitHubAPIRateLimit: opts.MaxGitHubAPIRateLimit,
rateLimitState: opts.rateLimitState,
@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Warning

Firewall blocked 1 domain

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

  • ab.chatgpt.com

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

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"

See Network Configuration for more information.

Generated by Ponytail Reviewer for #60531

@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

Warning

Firewall blocked 1 domain

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

  • proxy.golang.org

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

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

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

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

Copy link
Copy Markdown
Contributor
🏗️ ADR Required — draft added for PR #60531

An ADR was required for this PR because it adds more than 100 new lines in business logic directories (pkg/) and no existing ADR for this change was found in the PR body or branch.

Evidence reviewed

  • PR title: Share API rate-limit state across multi-target logs downloads
  • Diff files: pkg/cli/logs_multi.go, pkg/cli/logs_rate_limit.go, pkg/cli/logs_orchestrator*.go, related tests, and CLI help text
  • Existing ADR search on branch: latest ADRs were docs/adr/60310-... through docs/adr/60452-...; none covered PR 60531

Draft ADR added

  • docs/adr/60531-share-rate-limit-state-across-multi-target-logs-downloads.md

Gate result

This PR now has a draft Michael Nygard ADR capturing:

  • the architectural decision to share API rate-limit state across multi-target gh aw logs downloads
  • the driver to stop queued/in-flight sibling targets promptly at the shared ceiling
  • realistic alternatives and trade-offs
  • consequences, including resumability via continuation data

Next action

Please review and refine the draft ADR, then keep it with the PR as the decision record for this behavior change.

Warning

Firewall blocked 1 domain

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

  • proxy.golang.org

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

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · pi · gpt54 · 21.1 AIC · ⊞ 9.9K · ◷
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.

This is a Go/CLI change (concurrency + rate-limit coordination logic), not a UI change, so I applied a correctness-focused review instead of Impeccable UI modes.

Found two blocking issues in the new shared rate-limit coordination path, both confirmed by tracing the actual control flow:

  1. pkg/cli/logs_multi.go:355 — queued targets get a non-nil err when the ceiling is reached (unlike the parallel count-limit branch), which can turn a "ceiling reached before any run produced" scenario into a hard errors.Join failure instead of the graceful handleEmptyProcessedRuns path used for count limits.
  2. pkg/cli/logs_orchestrator_download.go:425 — a ceiling hit mid-batch is only recorded on the per-run DownloadResult.Error; logsProcessWorkflowRunBatch has no error return, so remaining runs in the same batch are misreported as plain failed downloads instead of being attributed to the rate limit and included in the continuation.

Details and suggested fixes are in the inline comments.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 117.9 AIC · ⌖ 14 AIC · ⊞ 8.4K

Comment thread pkg/cli/logs_multi.go
return logsTargetResult{target: target, result: countLimitedLogsTargetResult(targetOpts)}
}
if shared.rateLimitState.isReached() {
return logsTargetResult{target: target, result: rateLimitedLogsTargetResult(targetOpts), err: errLogsAPIRateLimitReached}

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.

collectSingleLogsTarget sets err: errLogsAPIRateLimitReached for every queued target once the ceiling is reached, unlike the analogous count-limit path a few lines above which returns err: nil (countLimitedLogsTargetResult(targetOpts) with no err). In mergeLogsTargetResults, every non-nil err is appended to allErrors, and DownloadWorkflowLogsForTargets has:

if len(processedRuns) == 0 {
    if len(allErrors) > 0 {
        return errors.Join(allErrors...)
    }
    _, err := handleEmptyProcessedRuns(...) // graceful path with continuations
    return err
}

So when the ceiling is hit before any target has produced a processed run (e.g. it's reached very early, or --count is small), every target — the one that detected the ceiling and all the queued ones — contributes an error, allErrors is non-empty, and the whole multi-target download now returns a hard error via errors.Join, instead of taking the graceful handleEmptyProcessedRuns branch that the count-limit case takes (which reports "no runs" plus the built continuations, exit 0). This turns an expected/handled condition (ceiling reached) into a CLI failure and discards the otherwise-correctly-built continuation data for the caller.

Suggested fix: don't set err on the logsTargetResult for the rate-limit-reached branches (mirror the count-limit branches), or explicitly filter errLogsAPIRateLimitReached out of allErrors before the len(allErrors) > 0 hard-fail check in DownloadWorkflowLogsForTargets.

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

Addressed in b5f569b and follow-up commits: rate-limit sentinel errors are now excluded from multi-target hard failures, preserving merged continuations.

maxConcurrentDownloads: opts.maxConcurrentDownloads,
storageLimit: runtime.storageLimit,
maxGitHubAPIRateLimit: opts.MaxGitHubAPIRateLimit,
rateLimitState: opts.rateLimitState,

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.

When the shared ceiling is tripped mid-batch (inside processSingleRunDownload → waitForConfiguredRateLimit → state.check), the resulting errLogsAPIRateLimitReached is stored only on the per-run DownloadResult.Error via handleArtifactDownloadError, and downloadRunArtifactsConcurrent/appendProcessedWorkflowRuns/processWorkflowRunBatch/logsProcessWorkflowRunBatch have no error return path to surface it.

shouldSkipProcessedWorkflowRun treats any non-nil result.Error as an ordinary failed download (logs a "Failed to download artifacts for run %d" warning and skips it), so every remaining run already queued in this batch's chunk also independently calls waitForConfiguredRateLimit, gets the cheap "already reached" short-circuit from logsRateLimitState.check, and is reported/logged as a plain download failure rather than being recognized as rate-limited — even though finishLogsBatch only inspects allRunsConsumed/batchStorageLimitReached (no rate-limit flag) before letting the loop continue to the next batch. The correct "stop with rate-limit continuation" behavior only kicks in on the next iteration's waitForLogsRateLimit call in collectProcessedWorkflowRuns.

Net effect: once the ceiling is hit inside a batch, the rest of that batch's runs are silently misreported as failed/errored downloads (noisy warnings, potentially confusing skip semantics) instead of being cleanly attributed to the rate limit and included in the continuation, even though the shared state (rateLimitState.isReached()) is already available to processWorkflowRunBatchOptions.

Consider checking opts.rateLimitState.isReached() in orderedLogsRunCollector.processReadyResult (similar to the existing opts.countLimit.isReached() check) to stop accepting/downloading further runs in the current batch and thread that state back through logsProcessWorkflowRunBatch's return values so finishLogsBatch can stop the batch immediately instead of continuing to process runs that are already known to be rate-limited.

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

Addressed in b5f569b and follow-up commits: a rate limit reached during batch processing returns the sentinel before cursor advancement, preserves relevant batch state, and is covered by regression tests.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@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 — the two unresolved existing bot review comments identify real correctness bugs in the rate-limit-ceiling continuation path, and this PR adds no regression test covering either scenario.

📋 Key Themes & Highlights

Key Themes

  • Untested continuation-drop path: When the shared rate ceiling is hit before any target produces a run, DownloadWorkflowLogsForTargets (logs_multi.go:183-188) returns early on len(allErrors) > 0 before continuations reach handleEmptyProcessedRuns or JSON rendering — so resumable continuation data for every queued target is silently lost. No test drives this path end-to-end.
  • Untested mid-batch cursor advance: fetchAndProcessLogsBatch → finishLogsBatch (logs_orchestrator_download.go:411-434) doesn't check opts.rateLimitState.isReached() before it may advance state.beforeDate past runs canceled mid-batch, since logsProcessWorkflowRunBatch has no error return. TestCollectLogsTargetsClearsQueueWhenRateLimitReached only exercises collectLogsTargets, not a mid-batch trip, so this gap isn't caught by CI.
  • Both issues are consistent with the design intent stated in the PR description ("Preserve continuation parameters for interrupted targets") but the implementation doesn't fully deliver on it in these two spots.

Positive Highlights

  • ✅ logsRateLimitState cleanly centralizes the shared gate/reached/cancel state and is threaded consistently through options structs (concurrentRunDownloadParams, processWorkflowRunBatchOptions, LogsDownloadOptions).
  • ✅ Good regression tests already exist for the state machine itself (TestLogsRateLimitStateStopsAndReusesReachedState, TestNilLogsRateLimitStatePreservesSingleTargetWait) and for the "don't start queued targets" behavior.
  • ✅ CLI/MCP help text updates for --max-github-api-rate-limit accurately describe the new single- vs multi-target semantics.

Requesting changes — please add regression tests (or fix the underlying gaps) for the two flagged continuation-handling paths before merge; see inline comments for specifics.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 136.6 AIC · ⌖ 15.2 AIC · ⊞ 10.4K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/cli/logs_multi.go:172

[/tdd] Confirms the existing bot finding at line 355: when the rate ceiling is hit before any target produces a run, DownloadWorkflowLogsForTargets returns errors.Join(allErrors...) at line 185 without ever reaching handleEmptyProcessedRuns, so continuations (built by mergeLogsTargetResults) are silently dropped from the report/JSON output.

<details>
<summary>💡 Suggested test</summary>

No test in logs_multi_test.go exercises the zero-processedRuns path end-to-end through `D…

pkg/cli/logs_orchestrator_download.go:434

[/tdd] Confirms the existing bot finding at line 425: logsProcessWorkflowRunBatch returns no error, so a rate-limit ceiling tripped mid-batch (visible only via shared.rateLimitState.isReached()) isn't checked here before calling finishLogsBatch, which can advance state.beforeDate past runs that were actually canceled.

<details>
<summary>💡 Suggested test</summary>

None of TestCollectLogsTargetsClearsQueueWhenRateLimitReached (logs_multi_test.go) or the rate-limit state tests (lo…

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 4 commits September 13, 2026 02:07
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Completed the PR-finisher pass. Latest commit: 538866f. Local change-scoped validation passes; final automated review found no comments.

@pelikhan
pelikhan merged commit 0489fac into main Sep 13, 2026
1 check failed
@pelikhan
pelikhan deleted the copilot/update-logs-command-rate-limit branch September 13, 2026 02:26
Copilot stopped work on behalf of pelikhan due to an error September 13, 2026 02:26
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.89.10

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.

3 participants