microsoft / microsoft/vscode

NES 402 handler resets the Copilot token in an unbounded loop (quota latch never engages)

Open
#331,579 0 comments 0 reactions 3 assignees Claimed by @dmitrivMS View on GitHub
bug NES network
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

The 402 handler in `completionsFetchServiceImpl.ts` is meant to refresh the token once so a quota latch flips and suppresses further resets. The latch never engages, so every 402 triggers a token reset — indefinitely.

```ts
// extensions/copilot/src/platform/nesFetch/node/completionsFetchServiceImpl.ts
if (response.status === 402) {
if (!this.authService.copilotToken?.isCompletionsQuotaExceeded) {
this.authService.resetCopilotToken(response.status);
await this.authService.getCopilotToken();
}
}
```

**Root cause.** The guard reads the wrong quota bucket:

```ts
// extensions/copilot/src/platform/authentication/common/copilotToken.ts
get isCompletionsQuotaExceeded(): boolean {
return this.isFreeUser && (this._info.limited_user_quotas?.completions ?? 1) <= 0;
}
```

`limited_user_quotas.completions` is free-tier **code-completions** quota, but the 402 originates from **NES**. A user can be out of NES allowance while `completions` stays > 0 — or the field is absent, where `?? 1` defaults to "quota available". The condition is unsatisfiable for this 402, so the re-mint is a no-op and the next request repeats it.

Secondary: the `isFreeUser &&` prefix makes the latch unconditionally false for any paid user. Latent while 402s are free-tier, but it activates as soon as paid users hit NES 402s.

`chatMLFetcher.ts` has the same shape via `isChatQuotaExceeded` (HTTP 402 branch and the CAPI `quota_exceeded` / `free_quota_exceeded` / `overage_limit_reached` / `billing_not_configured` / `additional_spend_limit_reached` branch).

**Observed behavior.** Measured over 24h in a downstream consumer of this code (GitHub Copilot Language Server, which ships it via `@vscode/chat-lib`), across free-tier users hitting NES 402s — share of users by how many token resets they performed:

| Resets per user per day | Share of affected users |
| --- | --- |
| 1 (the intended one-shot) | 2% |
| 2–10 | 5% |
| more than 100 | 78% |
| more than 1000 | 27% |

Only ~2% of affected users follow the intended path. For the rest the re-mint provably never changes the state the guard tests, so the reset repeats at request rate — we've observed sustained per-minute reset rates on individual devices. The affected population has been growing week over week.

**Impact.** Repeated token minting from clients that cannot benefit, plus wasted CPU and network on affected machines. The same code path runs in the VS Code Copilot extension, so the spin may occur there too; we have no visibility into that population to confirm.

**Suggested fix.**
1. Latch on the quota that produced the 402 (or on the 402 itself / `retry-after`), not `limited_user_quotas.completions`.
2. Drop the `isFreeUser &&` prefix so paid users are covered.
3. Add a cap/backoff so a non-latching guard cannot spin at request rate.

**Source.** Confirmed present on `main` today in `extensions/copilot/src/platform/nesFetch/node/completionsFetchServiceImpl.ts` and `extensions/copilot/src/platform/authentication/common/copilotToken.ts`. Also reproduced via `@vscode/chat-lib` 0.60.0, whose published `repository` field still points at the now-archived `microsoft/vscode-copilot-chat`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.