Skip to content

Fix 429 rate limiting in evaluation pipeline - #284

Merged
JanKrivanek merged 3 commits into
mainfrom
fix/429-rate-limiting
Mar 8, 2026
Merged

Fix 429 rate limiting in evaluation pipeline#284
JanKrivanek merged 3 commits into
mainfrom
fix/429-rate-limiting

Conversation

@JanKrivanek

Copy link
Copy Markdown
Member

Summary

Fixes the 69% failure rate on scheduled evaluation runs caused by Copilot API 429 rate limits after the multi-plugin split in #274.

Changes (layered approach)

P0: Retry on ListModelsAsync() (immediate crash fix)

Wraps the ListModelsAsync() call in ValidateCommand.Run() with RetryHelper.ExecuteWithRetry (3 retries, 2s exponential backoff, 60s budget). This directly fixes the crash a 429 on models.list is transient and resolves within seconds.

P1: Reduce scheduled-run parallelism

Scheduled runs now use the same reduced concurrency as infra-change runs (2/3/3 instead of 5/5/5). Since scheduled runs also spawn all 5 plugin matrix jobs simultaneously, they should avoid compounding the load.

P2: Deterministic token sharding

Replaces random token selection (RANDOM % N) with deterministic assignment by strategy.job-index. With 5 tokens and 5 matrix jobs, this guarantees each job gets a unique token eliminating the ~96% chance of token collisions.

P3: Retry on RunAgent()

Wraps AgentRunner.RunAgent() with RetryHelper.ExecuteWithRetry (2 retries, 5s backoff, scenario timeout + 60s budget) to handle mid-evaluation 429s.

Validation

  • Build passes
  • All 280 tests pass

Analysis

See docs/fix-429-rate-limiting.md for the full root cause analysis.

Fixes #168

- P0: Add retry with exponential backoff to ListModelsAsync() in ValidateCommand
  using the existing RetryHelper (3 retries, 2s base delay, 60s budget)
- P1: Reduce scheduled-run parallelism to match infra-change settings (2/3/3)
  to avoid overloading the API when all 5 plugins run in parallel
- P2: Assign Copilot tokens deterministically by matrix job index instead of
  randomly, eliminating ~96% token collision probability
- P3: Add retry with backoff to AgentRunner.RunAgent() for mid-eval 429s
  (2 retries, 5s base delay, scenario timeout + 60s budget)

Fixes #168

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 aims to reduce Copilot API 429 rate-limit failures in the evaluation pipeline after the multi-plugin split by adding targeted retries and reducing/token-sharding concurrency pressure in scheduled runs.

Changes:

  • Add retry/backoff around ValidateCommand.Run()’s ListModelsAsync() to avoid immediate job failure on transient 429s.
  • Reduce scheduled-run per-job concurrency in evaluation.yml to match the “infra change” lower-parallelism settings.
  • Replace random Copilot token selection with deterministic sharding via strategy.job-index in evaluation-run.yml.
  • Add a new root-cause analysis doc and wrap AgentRunner.RunAgent() with RetryHelper (but see comments re: effectiveness).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
eng/skill-validator/src/Services/AgentRunner.cs Wraps RunAgent with retry helper to improve resilience to transient failures during agent runs.
eng/skill-validator/src/Commands/ValidateCommand.cs Retries ListModelsAsync() to prevent transient 429s from failing validation immediately.
docs/fix-429-rate-limiting.md Documents the incident analysis and layered mitigation plan.
.github/workflows/evaluation.yml Lowers scheduled-run concurrency inputs to reduce rate-limit pressure.
.github/workflows/evaluation-run.yml Deterministically assigns Copilot tokens per matrix job to avoid token collisions.
Comments suppressed due to low confidence (1)

docs/fix-429-rate-limiting.md:17

  • In the root-cause chain, step 2 says each job "randomly selects" a token, but this PR switches selection to deterministic sharding by strategy.job-index. Consider clarifying that this list describes the pre-fix behavior (or update it to reflect the new behavior) so the doc doesn’t become misleading post-merge.
1. The evaluation matrix now spawns **5 parallel jobs** (one per plugin) instead of 1.
2. Each job randomly selects one of 8 Copilot tokens and immediately calls `ListModelsAsync()` to validate the model.
3. With 5 jobs starting simultaneously, multiple jobs may pick the same token and hit the Copilot API `models.list` endpoint concurrently, triggering a 429.
4. `ListModelsAsync()` in `ValidateCommand.Run()` has **zero retry logic** — any exception immediately exits with code 1.

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

You can also share your feedback on Copilot code review. Take the survey.

Comment thread eng/skill-validator/src/Services/AgentRunner.cs Outdated
Comment thread eng/skill-validator/src/Commands/ValidateCommand.cs
Comment thread docs/fix-429-rate-limiting.md Outdated
@JanKrivanek
JanKrivanek enabled auto-merge (squash) March 8, 2026 19:40
- Change unused ct param to _ in ListModelsAsync retry lambda since the
  SDK method has no CancellationToken overload
- Pass CancellationToken from RetryHelper through to RunAgentCore and
  link it with the per-scenario CTS so budget expiry cancels in-flight work
- Re-throw 429/rate-limit errors in RunAgentCore instead of swallowing
  them, so RetryHelper can actually retry on transient rate limits
- Doc comment (re: stale table) is moot  the doc was already deleted
@JanKrivanek
JanKrivanek merged commit dcdad22 into main Mar 8, 2026
14 checks passed
@JanKrivanek
JanKrivanek deleted the fix/429-rate-limiting branch March 8, 2026 20:11
@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Skill Validation Results

Skill Scenario Baseline With Skill Δ Skills Loaded Overfit Verdict
csharp-scripts Test a C# language feature with a script 3.0/5 5.0/5 +2.0 ✅ csharp-scripts; tools: skill, create, edit 🟡 0.31
nuget-trusted-publishing Set up trusted publishing for a new NuGet library 1.0/5 ⏰ timeout 4.0/5 +3.0 ✅ nuget-trusted-publishing; tools: skill ✅ 0.10
nuget-trusted-publishing Set up NuGet publishing without mentioning trusted publishing 2.0/5 5.0/5 +3.0 ✅ nuget-trusted-publishing; tools: skill, report_intent, view, glob, bash, stop_bash ✅ 0.10
nuget-trusted-publishing Migrate existing workflow from API key to trusted publishing 3.0/5 4.0/5 +1.0 ✅ nuget-trusted-publishing; tools: skill ✅ 0.10
dotnet-pinvoke Generate LibraryImport declaration from C header (.NET 8+) 4.0/5 5.0/5 +1.0 ✅ dotnet-pinvoke; tools: skill ✅ 0.06
dotnet-pinvoke Generate LibraryImport declaration from C header (.NET Framework) 5.0/5 5.0/5 0.0 ✅ dotnet-pinvoke; tools: skill ✅ 0.06
dotnet-trace-collect High CPU in Kubernetes on Linux (.NET 8) 4.0/5 5.0/5 +1.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect .NET Framework on Windows without admin privileges 2.0/5 5.0/5 +3.0 ✅ dotnet-trace-collect; tools: skill ✅ 0.12
dotnet-trace-collect .NET 10 on Linux with root access and native call stacks 1.0/5 4.0/5 +3.0 ✅ dotnet-trace-collect; tools: skill ✅ 0.12
dotnet-trace-collect Memory leak on Linux (.NET 8) 3.0/5 3.0/5 0.0 ✅ dotnet-trace-collect; tools: skill ✅ 0.12
dotnet-trace-collect Slow requests on Windows with PerfView 3.0/5 5.0/5 +2.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Excessive GC on Linux (.NET 8) 4.0/5 5.0/5 +1.0 ✅ dotnet-trace-collect; tools: skill ✅ 0.12
dotnet-trace-collect Hang or deadlock diagnosis on Linux 3.0/5 3.0/5 0.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Windows container high CPU with PerfView 2.0/5 5.0/5 +3.0 ✅ dotnet-trace-collect; tools: report_intent, skill, view ✅ 0.12
dotnet-trace-collect Long-running intermittent issue with PerfView triggers 4.0/5 5.0/5 +1.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Linux pre-.NET 10 needing native call stacks 4.0/5 5.0/5 +1.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view, glob ✅ 0.12
dotnet-trace-collect Windows modern .NET with admin high CPU 3.0/5 5.0/5 +2.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Memory leak on .NET Framework Windows 3.0/5 5.0/5 +2.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Kubernetes with console access prefers console tools 4.0/5 5.0/5 +1.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Container installation without .NET SDK 4.0/5 4.0/5 0.0 ✅ dotnet-trace-collect; tools: skill, view ✅ 0.12
dotnet-trace-collect HTTP 500s from downstream service on Linux (.NET 8) 4.0/5 5.0/5 +1.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
dotnet-trace-collect Networking timeouts on Windows with admin (.NET 8) 2.0/5 5.0/5 +3.0 ✅ dotnet-trace-collect; tools: skill, report_intent, view ✅ 0.12
microbenchmarking Investigate runtime upgrade performance impact 3.0/5 4.0/5 +1.0 ✅ microbenchmarking; tools: skill ✅ 0.11
clr-activation-debugging Diagnose unexpected FOD dialog from native build tool 1.0/5 5.0/5 +4.0 ✅ clr-activation-debugging; tools: skill ✅ 0.07
clr-activation-debugging Diagnose FOD suppressed but activation still failing 1.0/5 5.0/5 +4.0 ✅ clr-activation-debugging; tools: skill ✅ 0.07
clr-activation-debugging Explain why same binary behaves differently under different launch methods 1.0/5 5.0/5 +4.0 ✅ clr-activation-debugging; tools: skill ✅ 0.07
clr-activation-debugging Analyze healthy managed EXE activation 1.0/5 5.0/5 +4.0 ✅ clr-activation-debugging; tools: skill ✅ 0.07
clr-activation-debugging Identify multiple activation sequences in a single log 1.0/5 5.0/5 +4.0 ✅ clr-activation-debugging; tools: skill, task, glob ✅ 0.07
clr-activation-debugging Explain useLegacyV2RuntimeActivationPolicy in activation log 2.0/5 4.0/5 +2.0 ✅ clr-activation-debugging; tools: skill ✅ 0.07
clr-activation-debugging Decline non-CLR-activation issue 1.0/5 5.0/5 +4.0 ℹ️ not activated (expected) ✅ 0.07
analyzing-dotnet-performance Detects compiled regex startup budget and regex chain allocations 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Detects CurrentCulture comparer and compiled regex budget in inflection rules 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Finds per-call Dictionary allocation not hoisted to static 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Catches compound allocations in recursive number converter with ToLower 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Finds StringComparison.Ordinal missing and FrozenDictionary opportunities 1.0/5 4.0/5 +3.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Detects Aggregate+Replace chain and struct missing IEquatable 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Finds branched Replace chain in format string manipulation 1.0/5 4.0/5 +3.0 ✅ analyzing-dotnet-performance; tools: skill, task, glob, read_agent ✅ 0.14
analyzing-dotnet-performance Catches LINQ on hot-path string processing and All(char.IsUpper) 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Detects LINQ pipeline in TimeSpan formatting and collection processing 1.0/5 4.0/5 +3.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
analyzing-dotnet-performance Flags Span inconsistencies and compound method chains in truncation library 1.0/5 5.0/5 +4.0 ✅ analyzing-dotnet-performance; tools: skill, read_bash, stop_bash ✅ 0.14
analyzing-dotnet-performance Identifies unsealed leaf classes and locale hierarchy patterns 1.0/5 5.0/5 ⏰ timeout +4.0 ✅ analyzing-dotnet-performance; tools: skill ✅ 0.14
android-tombstone-symbolication Symbolicate .NET frames in an Android tombstone 4.0/5 4.0/5 0.0 ✅ android-tombstone-symbolication; tools: skill, glob ✅ 0.18
android-tombstone-symbolication Recognize tombstone with no .NET frames 5.0/5 5.0/5 0.0 ✅ android-tombstone-symbolication; tools: skill ✅ 0.18
android-tombstone-symbolication Symbolicate CoreCLR frames in an Android tombstone 4.0/5 4.0/5 0.0 ✅ android-tombstone-symbolication; tools: skill, glob ✅ 0.18
android-tombstone-symbolication Recognize NativeAOT tombstone with app binary and libSystem.Native.so 3.0/5 4.0/5 +1.0 ✅ android-tombstone-symbolication; tools: skill, bash, glob ✅ 0.18
android-tombstone-symbolication Symbolicate multi-thread tombstone 5.0/5 4.0/5 -1.0 ✅ android-tombstone-symbolication; tools: skill, glob ✅ 0.18
android-tombstone-symbolication Handle .NET frames with no BuildId metadata 4.0/5 5.0/5 +1.0 ✅ android-tombstone-symbolication; tools: skill ✅ 0.18
android-tombstone-symbolication Symbolicate tombstone with multiple .NET libraries and different BuildIds 3.0/5 4.0/5 +1.0 ✅ android-tombstone-symbolication; tools: skill ✅ 0.18
android-tombstone-symbolication Reject iOS crash log as wrong format 5.0/5 5.0/5 0.0 ℹ️ not activated (expected) ✅ 0.18
dump-collect Configure automatic crash dumps for CoreCLR app on Linux 5.0/5 5.0/5 0.0 ✅ dump-collect; tools: skill, report_intent, view, glob 🟡 0.21
dump-collect Set up NativeAOT crash dumps with createdump in Kubernetes 2.0/5 5.0/5 +3.0 ✅ dump-collect; tools: skill 🟡 0.21
dump-collect Recover crash dump from macOS NativeAOT without createdump 4.0/5 4.0/5 0.0 ✅ dump-collect; tools: skill, report_intent, view, glob, bash 🟡 0.21
dump-collect Configure CoreCLR dump collection in Alpine Docker as non-root 5.0/5 5.0/5 0.0 ✅ dump-collect; tools: skill, report_intent, view, glob, bash 🟡 0.21
dump-collect Advisory: macOS NativeAOT crash dump recovery steps 4.0/5 4.0/5 0.0 ✅ dump-collect; tools: skill, glob 🟡 0.21
dump-collect Advisory: CoreCLR Alpine Docker non-root configuration 4.0/5 5.0/5 +1.0 ✅ dump-collect; tools: report_intent, skill, view 🟡 0.21
dump-collect Advisory: NativeAOT Kubernetes dump collection setup 3.0/5 5.0/5 +2.0 ✅ dump-collect; tools: skill 🟡 0.21
dump-collect Detect runtime and configure crash dumps for unknown .NET app on Linux 4.0/5 4.0/5 0.0 ✅ dump-collect; tools: skill 🟡 0.21
dump-collect Decline dump analysis request 2.0/5 5.0/5 +3.0 ℹ️ not activated (expected) 🟡 0.21
optimizing-ef-core-queries Optimize bulk operations with EF Core 7+ ExecuteUpdate and ExecuteDelete 5.0/5 4.0/5 -1.0 ✅ optimizing-ef-core-queries; tools: report_intent, skill 🟡 0.42
build-parallelism Analyze build parallelism bottlenecks 4.0/5 5.0/5 +1.0 ✅ build-parallelism; binlog-generation; tools: skill, task, glob 🟡 0.26
including-generated-files Diagnose generated file inclusion failure 3.0/5 5.0/5 +2.0 ✅ including-generated-files; tools: skill 🟡 0.24
msbuild-antipatterns Review MSBuild files for anti-patterns and style issues 5.0/5 5.0/5 0.0 ✅ msbuild-antipatterns; tools: skill, glob, edit ✅ 0.06
build-perf-baseline Establish build performance baseline and recommend optimizations 3.0/5 4.0/5 +1.0 ✅ build-perf-baseline; build-perf-diagnostics; check-bin-obj-clash; tools: skill, edit 🟡 0.36
msbuild-modernization Modernize legacy project to SDK-style 5.0/5 5.0/5 0.0 ✅ msbuild-modernization; tools: skill ✅ 0.08
directory-build-organization Organize build infrastructure for a multi-project repo 3.0/5 5.0/5 +2.0 ✅ directory-build-organization; msbuild-antipatterns; tools: skill ✅ 0.15
check-bin-obj-clash Diagnose bin/obj output path clashes 4.0/5 5.0/5 +1.0 ✅ check-bin-obj-clash; binlog-generation; tools: glob, skill, edit ✅ 0.14
incremental-build Analyze incremental build issues 3.0/5 3.0/5 0.0 ✅ incremental-build; tools: skill ✅ 0.14
eval-performance Analyze MSBuild evaluation performance issues 4.0/5 4.0/5 0.0 ✅ eval-performance; tools: skill ✅ 0.07
build-perf-diagnostics Analyze analyzer performance impact on builds 5.0/5 5.0/5 0.0 ✅ binlog-generation; build-perf-diagnostics; binlog-failure-analysis; tools: skill 🟡 0.24
binlog-generation Build project with /bl flag 1.0/5 5.0/5 +4.0 ✅ binlog-generation; tools: skill ✅ 0.00
binlog-generation Build with /bl in PowerShell 3.0/5 5.0/5 +2.0 ✅ binlog-generation; tools: skill ✅ 0.00
binlog-generation Build multiple configurations with unique binlogs 3.0/5 5.0/5 +2.0 ✅ binlog-generation; tools: skill ✅ 0.00
binlog-failure-analysis Diagnose build failures from binlog only (no source files) 4.0/5 5.0/5 +1.0 ✅ binlog-failure-analysis; tools: skill ✅ 0.04
thread-abort-migration Worker thread with abort-based cancellation 5.0/5 5.0/5 0.0 ✅ thread-abort-migration; tools: skill ✅ 0.11
thread-abort-migration Timeout enforcement via Thread.Abort 4.0/5 4.0/5 0.0 ✅ thread-abort-migration; tools: skill ✅ 0.11
thread-abort-migration Blocking WaitHandle with Thread.Interrupt 4.0/5 5.0/5 +1.0 ✅ thread-abort-migration; tools: skill ✅ 0.11
thread-abort-migration ASP.NET Response.End and Response.Redirect with Thread.Abort 4.0/5 5.0/5 +1.0 ✅ thread-abort-migration; tools: skill ✅ 0.11
thread-abort-migration Thread.Join and Thread.Sleep only — should not migrate 3.0/5 5.0/5 +2.0 ✅ thread-abort-migration; tools: skill ✅ 0.11
migrate-nullable-references Enable NRT in a small library with mixed nullability 5.0/5 5.0/5 0.0 ✅ migrate-nullable-references; tools: skill ✅ 0.05
migrate-nullable-references File-by-file migration: only modify the targeted file 5.0/5 5.0/5 0.0 ⚠️ NOT ACTIVATED ✅ 0.05
migrate-nullable-references Enable NRT in ASP.NET Core Web API with EF Core 4.0/5 3.0/5 -1.0 ✅ migrate-nullable-references; tools: skill ✅ 0.05
dotnet-aot-compat Make Azure.ResourceManager AOT-compatible 1.0/5 4.0/5 ⏰ timeout +3.0 ✅ dotnet-aot-compat; tools: skill, create, read_agent ✅ 0.12

timeout — run hit the scenario timeout limit; scoring may be impacted by aborting model execution before it could produce its full output

Model: claude-opus-4.6 | Judge: claude-opus-4.6

Full results

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.

🏥 Repository Health Dashboard

4 participants