redline
← arena

Partial, as declared

SeniorTypeScriptHTTP clientMined
MINED FROM REAL HISTORYsindresorhus/gotMIT

The code below is the file exactly as it stood in sindresorhus/got. The defect was introduced on 2026-02-24, reviewed, merged, and fixed on 2026-09-01 in commit 8f44d815 — after 189 days in the tree.

The commit was written by a human contributor to sindresorhus/got, not by an agent. The ticket and the description on the next two panels are ours — a reconstruction of the case the original change made, so you meet it the way its reviewer did. The code, the defect and the dates are untouched.

NET-77the ticket

Let callers set retry options after constructing the client

`retry` can only be configured at construction time today. Callers that tune retries per call site — a longer limit for a flaky partner API, retries off entirely for a non-idempotent POST — have to build a whole new client.

Make `retry` a settable property. Setting it should accept any subset of the retry options, validate the values, and reject unknown keys so a typo is an error rather than a silent no-op. The merge path used internally must keep behaving exactly as it does now.

the case the change madereconstructed by us

`retry` is now a validated setter accepting `Partial<RetryOptions>`.

Each field is type-checked individually and allowed to be `undefined`, so a caller can pass any subset. Unknown keys are rejected by checking membership against the current retry object, which means a typo like `retires: 3` throws instead of silently doing nothing — `__proto__` is skipped in that loop so a malicious key cannot walk the prototype chain.

The internal merge path is untouched: when `#merging` is set we assign onto the existing object exactly as before. Outside a merge the setter installs the caller's value. Methods are upper-cased and all three list options are de-duplicated afterwards, so the normalisation runs identically whichever path was taken.

Our reconstruction of the argument the real change made, not a quotation of it. Fluent, specific, and not evidence of anything — which is the point.

source/core/options.ts38 lines
1 set retry(value: Partial<RetryOptions>) {
2 assertPlainObject('retry', value);
3
4 assertAny('retry.calculateDelay', [is.function, is.undefined], value.calculateDelay);
5 assertAny('retry.maxRetryAfter', [is.number, is.undefined], value.maxRetryAfter);
6 assertAny('retry.limit', [is.number, is.undefined], value.limit);
7 assertAny('retry.methods', [is.array, is.undefined], value.methods);
8 assertAny('retry.statusCodes', [is.array, is.undefined], value.statusCodes);
9 assertAny('retry.errorCodes', [is.array, is.undefined], value.errorCodes);
10 assertAny('retry.noise', [is.number, is.undefined], value.noise);
11 assertAny('retry.enforceRetryRules', [is.boolean, is.undefined], value.enforceRetryRules);
12
13 if (value.noise && Math.abs(value.noise) > 100) {
14 throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
15 }
16
17 for (const key of Object.keys(value)) {
18 if (key === '__proto__') {
19 continue;
20 }
21
22 if (!(key in this.#internals.retry)) {
23 throw new Error(`Unexpected retry option: ${key}`);
24 }
25 }
26
27 if (this.#merging) {
28 safeObjectAssign(this.#internals.retry, value);
29 } else {
30 this.#internals.retry = {...value};
31 }
32
33 const {retry} = this.#internals;
34
35 retry.methods = [...new Set(retry.methods!.map(method => method.toUpperCase() as Method))];
36 retry.statusCodes = [...new Set(retry.statusCodes)];
37 retry.errorCodes = [...new Set(retry.errorCodes)];
38 }