{"id":"572e5697189cd2f0","repo":"sindresorhus/got","slug":"the-afterresponse-hook-returned-an-invalid-value","errorCode":null,"errorMessage":"The `afterResponse` hook returned an invalid value","messagePattern":"The `afterResponse` hook returned an invalid value","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"source/as-promise/index.ts","lineNumber":171,"sourceCode":"\t\t\t\t\t\t\t\t\t\t\tif (!isSameOrigin(previousUrl, nextUrl) && !hasExplicitBody) {\n\t\t\t\t\t\t\t\t\t\t\t\toptions.clearBody();\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Remove any further hooks for that request, because we'll call them anyway.\n\t\t\t\t\t\t\t\t// The loop continues. We don't want duplicates (asPromise recursion).\n\t\t\t\t\t\t\t\t// Unless preserveHooks is true, in which case we keep the remaining hooks.\n\t\t\t\t\t\t\t\tif (!preserveHooks) {\n\t\t\t\t\t\t\t\t\toptions.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tthrow new RetryError(request);\n\t\t\t\t\t\t\t}));\n\n\t\t\t\t\t\t\tif (!(is.object(response) && is.number(response.statusCode) && 'body' in response)) {\n\t\t\t\t\t\t\t\tthrow new TypeError('The `afterResponse` hook returned an invalid value');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\trequest._beforeError(normalizeError(error));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tglobalResponse = response;\n\n\t\t\t\t\tif (!isResponseOk(response)) {\n\t\t\t\t\t\trequest._beforeError(new HTTPError(response));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\trequest.destroy();\n\t\t\t\t\tpromiseSettled = true;\n\t\t\t\t\tresolve(request.options.resolveBodyOnly ? response.body as T : response as unknown as T);\n\t\t\t\t})();","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/sindresorhus/got/blob/e3924aa1e53a6ca3eb93a43618ce532442a89b40/source/as-promise/index.ts#L153-L189","documentation":"Thrown at source/as-promise/index.ts:171 after each afterResponse hook resolves. The wrapper expects every hook to yield back a response-shaped object: a plain object with a numeric `statusCode` and a `body` property. If the hook returns undefined, null, a string, or an object missing those fields, got cannot continue processing and throws this TypeError. The check exists because hooks can either return the (possibly mutated) response directly or call the retry callback; returning anything else is a contract violation that would crash later code that reads response.statusCode.","triggerScenarios":"An afterResponse hook that forgets to return (implicit undefined), returns `null` on some branch, returns a modified options object instead of the response, or returns a promise that resolves to a non-response value. Triggered on the first response after such a hook runs.","commonSituations":"Refactoring a hook to short-circuit on a status code and forgetting the return on the happy path; returning the parsed JSON body instead of the response object; migrating from got v11 where hook return semantics were looser; TypeScript users who typed the hook loosely (any) so the bug isn't caught at compile time.","solutions":["Always return the response object from afterResponse hooks — mutate it in place if you need to change headers or body.","If you need to retry, return the call to the retry function (the second hook argument) instead of returning a value.","Add an explicit return type annotation `: Response | RequestPromise<Response>` so the TypeScript compiler catches missing returns."],"exampleFix":"// before\nhooks: {\n  afterResponse: [(response, retry) => {\n    if (response.statusCode === 401) {\n      retry({ headers: { authorization: refresh() } }); // missing return\n    }\n    // missing return on happy path\n  }]\n}\n\n// after\nhooks: {\n  afterResponse: [(response, retry) => {\n    if (response.statusCode === 401) {\n      return retry({ headers: { authorization: refresh() } });\n    }\n    return response;\n  }]\n}","handlingStrategy":"validation","validationCode":"// Validate hook shape before registering it.\nfunction validateAfterResponseHook(hook) {\n  // Wrap to assert the return value at runtime.\n  return (response, retry) => {\n    const result = hook(response, retry);\n    const unwrap = (v) => {\n      if (v && typeof v.then === 'function') return v.then(unwrap);\n      if (v === undefined || v === null) {\n        throw new TypeError('afterResponse hook returned nothing — must return the response or call retry()');\n      }\n      if (typeof v.statusCode !== 'number' || !('body' in v)) {\n        throw new TypeError('afterResponse hook must return a response-shaped object');\n      }\n      return v;\n    };\n    return unwrap(result);\n  };\n}","typeGuard":"import type {Response, RequestPromise} from 'got';\n\nfunction isResponseLike(v: unknown): v is Response {\n  return typeof v === 'object' && v !== null\n    && typeof (v as any).statusCode === 'number'\n    && 'body' in (v as any);\n}","tryCatchPattern":"try {\n  await got(url, { hooks: { afterResponse: [hook] } });\n} catch (error) {\n  if (error instanceof TypeError && /afterResponse hook returned an invalid value/.test(error.message)) {\n    throw new Error('afterResponse hook contract violation: must return the response or retry()', { cause: error });\n  }\n  throw error;\n}","preventionTips":["Always return the response object (or the retry() call result) from afterResponse hooks.","Annotate hook types explicitly: `(response: Response, retry: (opts?) => RequestPromise<Response>) => Response | RequestPromise<Response>`.","Add a unit test that drives the hook with a mock response and asserts the return shape."],"tags":["hooks","after-response","typescript","contract-violation"],"analyzedSha":"e3924aa1e53a6ca3eb93a43618ce532442a89b40","analyzedAt":"2026-08-03T19:22:24.770Z","schemaVersion":2}