{"id":"a5a6562df4b93758","repo":"sindresorhus/got","slug":"unexpected-timeout-option-key","errorCode":null,"errorMessage":"Unexpected timeout option: ${key}","messagePattern":"Unexpected timeout option: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"source/core/options.ts","lineNumber":1862,"sourceCode":"\t- `send` starts when the socket is connected and ends with the request has been written to the socket.\n\t- `request` starts when the request is initiated and ends when the response's end event fires.\n\t*/\n\tget timeout(): Delays {\n\t\t// We always return `Delays` here.\n\t\t// It has to be `Delays | number`, otherwise TypeScript will error because the getter and the setter have incompatible types.\n\t\treturn this.#internals.timeout;\n\t}\n\n\tset timeout(value: Delays) {\n\t\tassertPlainObject('timeout', value);\n\n\t\tfor (const key of Object.keys(value)) {\n\t\t\tif (key === '__proto__') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!(key in this.#internals.timeout)) {\n\t\t\t\tthrow new Error(`Unexpected timeout option: ${key}`);\n\t\t\t}\n\n\t\t\t// @ts-expect-error - No idea why `value[key]` doesn't work here.\n\t\t\tassertAny(`timeout.${key}`, [is.number, is.undefined], value[key]);\n\t\t}\n\n\t\tif (this.#merging) {\n\t\t\tsafeObjectAssign(this.#internals.timeout, value);\n\t\t} else {\n\t\t\tthis.#internals.timeout = {...value};\n\t\t}\n\t}\n\n\t/**\n\tWhen specified, `prefixUrl` will be prepended to relative string `url` input.\n\tThe prefix can be any valid URL, either relative or absolute.\n\tA trailing slash `/` is optional - one will be added automatically.\n","sourceCodeStart":1844,"sourceCodeEnd":1880,"githubUrl":"https://github.com/sindresorhus/got/blob/e3924aa1e53a6ca3eb93a43618ce532442a89b40/source/core/options.ts#L1844-L1880","documentation":"Thrown by the `timeout` setter when the `timeout` object has a key that is not one of the supported request phases: `lookup`, `connect`, `secureConnect`, `socket`, `send`, `response`, `read`, `request`. Each timeout bounds a specific lifecycle phase, so unknown phase names are rejected to avoid silently not applying a timeout.","triggerScenarios":"Calling `got(url, {timeout: {headers: 5000}})` (no such phase), `{timeout: {total: 5000}}` (use `request` for the whole request), or any timeout object key outside the documented phases. Note Got v13+ removed the bare-number form, so `{timeout: 5000}` also fails type validation.","commonSituations":"Migrating from older Got where `timeout` could be a number or had different phase names; copying timeout config from another library; typos like `conect`.","solutions":["Use only documented phase keys: `lookup`, `connect`, `secureConnect`, `socket`, `send`, `response`, `read`, `request`.","For a whole-request timeout use `{timeout: {request: 5000}}`.","Check the `${key}` in the message and rename/remove it."],"exampleFix":"// before\nawait got(url, {timeout: {total: 5000}});\n// after\nawait got(url, {timeout: {request: 5000}});","handlingStrategy":"type-guard","validationCode":"const validPhases = new Set(['lookup','connect','secureConnect','socket','send','response','read','request']);\nfunction validateTimeout(timeout) {\n  for (const key of Object.keys(timeout ?? {})) {\n    if (!validPhases.has(key)) throw new Error(`Unknown timeout phase: ${key}`);\n  }\n}","typeGuard":"import type {Delays} from 'got';\nfunction isDelays(v: unknown): v is Delays {\n  if (typeof v !== 'object' || v === null) return false;\n  return Object.keys(v).every(k => ['lookup','connect','secureConnect','socket','send','response','read','request','__proto__'].includes(k));\n}","tryCatchPattern":null,"preventionTips":["Use the `Delays` type from Got so the compiler rejects bad keys.","Remember Got v13+ requires an object, not a bare number.","Use `{request: ms}` for a whole-request cap."],"tags":["options","timeout","validation","config"],"analyzedSha":"e3924aa1e53a6ca3eb93a43618ce532442a89b40","analyzedAt":"2026-08-03T19:22:24.770Z","schemaVersion":2}