Problem
During a large zstd C→Rust migration (310 tasks, maxParallelAgents: 12), the Copilot API started returning HTTP 429 rate-limit errors ~50 minutes into Phase 4. This caused a cascading failure:
- 68 agent invocations failed to emit
aamf-json output blocks because the Copilot CLI exited with code 1 after receiving a session.error with errorType: "rate_limit".
- Failed invocations burned retry budget — tasks that had genuine parity issues couldn't get enough clean attempts to converge.
- The run hit terminal exhaustion on task-157-0 after 7 retries (mix of rate-limited and real failures), halting the entire migration.
Breakdown by agent:
| Agent |
Missing aamf-json |
parity-failure-resolver |
45 |
parity-verifier |
15 |
code-migrator |
5 |
test-writer |
3 |
The rate limit error from the Copilot CLI looks like:
{
"type": "session.error",
"data": {
"errorType": "rate_limit",
"message": "Sorry, you've hit a rate limit... Please try again in 2 hours.",
"statusCode": 429
}
}
Current behavior
- Rate-limited invocations are treated identically to other failures — they consume retry attempts and are retried immediately.
maxParallelAgents controls the code-migrator branch concurrency, but each branch also spawns 2 sub-agents (parity-verifier + test-writer) via ParallelExecutor(2), so effective concurrent sessions can be up to maxParallelAgents * 3.
- No pause, backoff, or global throttle is applied when rate limits are detected.
Proposed behavior
-
Detect rate limits in agent output. In agent-launcher.ts, parse the agent's event log for session.error with errorType: "rate_limit" or check for exit code 1 with no aamf-json output and rate-limit markers in stderr/stdout.
-
Classify rate-limited invocations as transient infrastructure errors, not agent failures. They should not consume the task's maxRetriesPerTask budget.
-
Global backoff on rate limit detection. When any agent invocation hits a rate limit:
- Pause all new agent launches for a configurable backoff period (e.g., start at 60s, exponential up to the
retry-after value from the error if available).
- Log a clear warning:
"Rate limit detected — pausing all agent launches for {duration}s".
- Resume launches after the backoff expires.
-
Expose a rateLimitBackoff config option (optional):
{
"options": {
"rateLimitBackoff": {
"initialDelayMs": 60000,
"maxDelayMs": 7200000,
"backoffMultiplier": 2
}
}
}
Impact
Without this, any migration hitting Copilot rate limits will burn through retry budgets on transient errors and likely fail with terminal exhaustion. The workaround is to lower maxParallelAgents to 3-4, but this significantly increases wall-clock time for large codebases.
Related files
src/core/agent-launcher.ts — agent invocation and result parsing
src/execution/parallel-executor.ts — p-limit concurrency control
src/execution/retry.ts — retry logic
src/flow/steps/migration.ts — Phase 4 task orchestration
src/flow/steps/shared.ts — classifyError(), isTransientModelFailure()
Problem
During a large zstd C→Rust migration (310 tasks,
maxParallelAgents: 12), the Copilot API started returning HTTP 429 rate-limit errors ~50 minutes into Phase 4. This caused a cascading failure:aamf-jsonoutput blocks because the Copilot CLI exited with code 1 after receiving asession.errorwitherrorType: "rate_limit".Breakdown by agent:
parity-failure-resolverparity-verifiercode-migratortest-writerThe rate limit error from the Copilot CLI looks like:
{ "type": "session.error", "data": { "errorType": "rate_limit", "message": "Sorry, you've hit a rate limit... Please try again in 2 hours.", "statusCode": 429 } }Current behavior
maxParallelAgentscontrols the code-migrator branch concurrency, but each branch also spawns 2 sub-agents (parity-verifier + test-writer) viaParallelExecutor(2), so effective concurrent sessions can be up tomaxParallelAgents * 3.Proposed behavior
Detect rate limits in agent output. In
agent-launcher.ts, parse the agent's event log forsession.errorwitherrorType: "rate_limit"or check for exit code 1 with noaamf-jsonoutput and rate-limit markers in stderr/stdout.Classify rate-limited invocations as transient infrastructure errors, not agent failures. They should not consume the task's
maxRetriesPerTaskbudget.Global backoff on rate limit detection. When any agent invocation hits a rate limit:
retry-aftervalue from the error if available)."Rate limit detected — pausing all agent launches for {duration}s".Expose a
rateLimitBackoffconfig option (optional):{ "options": { "rateLimitBackoff": { "initialDelayMs": 60000, "maxDelayMs": 7200000, "backoffMultiplier": 2 } } }Impact
Without this, any migration hitting Copilot rate limits will burn through retry budgets on transient errors and likely fail with terminal exhaustion. The workaround is to lower
maxParallelAgentsto 3-4, but this significantly increases wall-clock time for large codebases.Related files
src/core/agent-launcher.ts— agent invocation and result parsingsrc/execution/parallel-executor.ts— p-limit concurrency controlsrc/execution/retry.ts— retry logicsrc/flow/steps/migration.ts— Phase 4 task orchestrationsrc/flow/steps/shared.ts—classifyError(),isTransientModelFailure()