Partial, as declared
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.
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.
`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.
set retry(value: Partial<RetryOptions>) { assertPlainObject('retry', value); assertAny('retry.calculateDelay', [is.function, is.undefined], value.calculateDelay); assertAny('retry.maxRetryAfter', [is.number, is.undefined], value.maxRetryAfter); assertAny('retry.limit', [is.number, is.undefined], value.limit); assertAny('retry.methods', [is.array, is.undefined], value.methods); assertAny('retry.statusCodes', [is.array, is.undefined], value.statusCodes); assertAny('retry.errorCodes', [is.array, is.undefined], value.errorCodes); assertAny('retry.noise', [is.number, is.undefined], value.noise); assertAny('retry.enforceRetryRules', [is.boolean, is.undefined], value.enforceRetryRules); if (value.noise && Math.abs(value.noise) > 100) { throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`); } for (const key of Object.keys(value)) { if (key === '__proto__') { continue; } if (!(key in this.#internals.retry)) { throw new Error(`Unexpected retry option: ${key}`); } } if (this.#merging) { safeObjectAssign(this.#internals.retry, value); } else { this.#internals.retry = {...value}; } const {retry} = this.#internals; retry.methods = [...new Set(retry.methods!.map(method => method.toUpperCase() as Method))]; retry.statusCodes = [...new Set(retry.statusCodes)]; retry.errorCodes = [...new Set(retry.errorCodes)]; }