GitHub API throttling and transient retry policy
This document defines the retry contract used by CopyGitHubRepo for GitHub API calls made through the GitHub CLI. It is intentionally conservative: bounded automatic retries are allowed only for read-only API requests where repeating the request cannot duplicate repository mutations.
Policy summary
| Request class | Automatic retry | Rationale |
|---|---|---|
| Read-only API discovery and state reads | Yes, for recognized transient failures only | Repeating a read does not create duplicate repository state. |
| Optional read-only API probes | Yes, for recognized transient failures only | The same read safety applies; ordinary 404 Not Found remains an absent-resource result rather than a retry condition. |
| Repository mutations | No | A failed response does not prove the mutation did not occur, so automatic retry could duplicate or obscure a partial change. |
| Git/Git LFS publication operations | Governed by their own execution/recovery behavior | These are not treated as generic GitHub API reads and must preserve partial-mutation evidence. |
Retryable read failures
The read adapter recognizes a deliberately narrow set of transient conditions surfaced by gh api, including:
- HTTP
429,502,503, and504responses; - primary or secondary GitHub rate-limit messages;
- abuse-detection responses;
- connection resets or connection timeouts;
- temporary DNS/network failures;
- TLS handshake timeouts;
- server disconnects; and
- unexpected EOF conditions.
Ordinary authorization, authentication, validation, permission, and not-found failures are not automatically retried unless they also contain a recognized transient condition. In particular, a normal 403 Forbidden fails immediately, and optional reads treat a normal 404 Not Found as an absent resource.
Bounded retry behavior
Read requests use at most three attempts by default. Between retryable failures, the adapter applies bounded exponential backoff with jitter. The default base delay is 250 milliseconds and the automatic backoff is capped at 2 seconds.
When GitHub supplies a numeric Retry-After delay in the surfaced diagnostic, that server guidance takes precedence over the locally calculated backoff. To avoid unexpectedly blocking an interactive or automation run for a long period, the adapter automatically waits only when the requested server delay is 60 seconds or less. If the server asks for a longer delay, the operation stops and reports the requested delay instead of retrying earlier than GitHub instructed.
A read that still fails after the bounded attempts reports how many attempts were made. Diagnostics are passed through the repository’s diagnostic-protection path before being included in the operator-visible error.
Mutation safety boundary
Invoke-CgrGitHubApiMutation deliberately does not use the read retry adapter. Even when a mutation receives a transient-looking error such as HTTP 503, the tool does not automatically repeat it.
This distinction is required because a network or service failure after a mutation request is sent does not establish whether GitHub applied the change. Automatically replaying the request could create duplicate, conflicting, or misleading state. The caller must instead preserve the failure and recovery evidence, inspect the current repository state, and choose a deliberate next action.
This rule applies even when a specific mutation appears likely to be idempotent. Automatic mutation retry must not be added unless the operation’s idempotency and response ambiguity are explicitly proven and covered by a separate product contract and tests.
Operator guidance
When a read-only GitHub API operation fails after retry exhaustion:
- Read the final error for the number of attempts and any server-provided retry guidance.
- Check GitHub service availability and local network connectivity if the failure is transient.
- For rate limiting, allow the GitHub-provided delay or reset window to pass before starting a new operation.
- Re-authenticate only when the diagnostic indicates an authentication or permission problem; ordinary
401/403failures are not transient retries. - If the failure happened before any mutation stage, correct the external condition and create/review a fresh plan when source state may have changed.
When a GitHub API mutation reports a failure:
- Do not assume the mutation failed just because the response failed.
- Do not blindly rerun the same mutation command.
- Inspect the relevant source, destination, and archive repository identities and current state.
- Preserve console output, structured recovery evidence, and the recorded failure stage.
- Follow
troubleshooting-recovery.mdbefore deciding whether a fresh planned invocation or manual recovery is safest.
Maintainer guidance
When adding or changing GitHub API calls:
- Route side-effect-free reads through the bounded read adapter unless there is a documented reason not to.
- Keep mutations on the non-retrying mutation adapter by default.
- Do not broaden the transient-error matcher merely to hide flaky failures; add a condition only when it is demonstrably transient and safe to retry.
- Keep retry counts and waits bounded so CI and interactive use cannot hang indefinitely.
- Honor server retry guidance rather than retrying earlier than requested.
- Add deterministic tests for any newly recognized retry condition and for the corresponding fail-fast boundary.
- Preserve protected, actionable diagnostics without exposing credentials or private values.
- Treat retries as resilience behavior, not as a substitute for recovery-state reporting after mutation.
Verification evidence
The retry contract is exercised by tests/GitHubApiAdapters.Tests.ps1. Coverage includes:
- one transient read failure followed by success;
- numeric
Retry-Afterhandling; - refusal to retry earlier than excessive server guidance;
- fail-fast behavior for ordinary authorization failures;
- bounded retry exhaustion diagnostics;
- optional
404absent-resource behavior; and - the invariant that mutation failures are never automatically retried.
The implementation lives in src/CopyGitHubRepo/Private/Invoke-CgrGitHubApiReadRequest.ps1, with the standard and optional read adapters routing through it. The mutation adapter remains intentionally separate.
Related documentation
- Recovery and partial-mutation handling:
troubleshooting-recovery.md - Product safety contract:
product-contract.md - Architecture and external-tool boundaries:
architecture.md - Non-functional requirements:
non-functional-requirements.md