{"id":"dde645746c7f40eb","repo":"sindresorhus/got","slug":"beforecache-hook-must-return-false-or-undefined-t","errorCode":null,"errorMessage":"beforeCache hook must return false or undefined. To modify the response, mutate it directly.","messagePattern":"beforeCache hook must return false or undefined\\. To modify the response, mutate it directly\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"source/core/index.ts","lineNumber":2031,"sourceCode":"\t\t\t\t\t\t\t\t\t// Prevent caching by adding no-cache headers\n\t\t\t\t\t\t\t\t\t// Mutate the response directly to add headers\n\t\t\t\t\t\t\t\t\tresponse.headers['cache-control'] = 'no-cache, no-store, must-revalidate';\n\t\t\t\t\t\t\t\t\tresponse.headers.pragma = 'no-cache';\n\t\t\t\t\t\t\t\t\tresponse.headers.expires = '0';\n\t\t\t\t\t\t\t\t\thandler(response);\n\t\t\t\t\t\t\t\t\t// Don't call remaining hooks - we've decided not to cache\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (is.promise(result)) {\n\t\t\t\t\t\t\t\t\t// BeforeCache hooks must be synchronous because cacheable-request's handler is synchronous\n\t\t\t\t\t\t\t\t\tthrow new TypeError('beforeCache hooks must be synchronous. The hook returned a Promise, but this hook must return synchronously. If you need async logic, use beforeRequest hook instead.');\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (result !== undefined) {\n\t\t\t\t\t\t\t\t\t// Hooks should return false or undefined only\n\t\t\t\t\t\t\t\t\t// Mutations work directly - no need to return the response\n\t\t\t\t\t\t\t\t\tthrow new TypeError('beforeCache hook must return false or undefined. To modify the response, mutate it directly.');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Else: void/undefined = continue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\tconst normalizedError = normalizeError(error);\n\t\t\t\t\t\t\t// Convert hook errors to RequestError and propagate\n\t\t\t\t\t\t\t// This is consistent with how other hooks handle errors\n\t\t\t\t\t\t\tif (gotRequest) {\n\t\t\t\t\t\t\t\tgotRequest._beforeError(normalizedError instanceof RequestError ? normalizedError : new RequestError(normalizedError.message, normalizedError, gotRequest));\n\t\t\t\t\t\t\t\t// Don't call handler when error was propagated successfully\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// If gotRequest is missing, log the error to aid debugging\n\t\t\t\t\t\t\t// We still call the handler to prevent the request from hanging\n\t\t\t\t\t\t\tconsole.error('Got: beforeCache hook error (request context unavailable):', normalizedError);\n\t\t\t\t\t\t\t// Call handler with response (potentially partially modified)\n\t\t\t\t\t\t\thandler(response);","sourceCodeStart":2013,"sourceCodeEnd":2049,"githubUrl":"https://github.com/sindresorhus/got/blob/e3924aa1e53a6ca3eb93a43618ce532442a89b40/source/core/index.ts#L2013-L2049","documentation":"Thrown at source/core/index.ts:2031 in the same beforeCache driver. Even if the hook is synchronous, it must still return one of two values: `false` (meaning 'do not cache this response', which the driver honors by adding no-cache headers and short-circuiting remaining hooks) or `undefined`/void (meaning 'continue, mutations applied directly'). Any other return value — a response object, a truthy boolean, a string — is rejected because the hook contract is mutate-in-place, not return-new-value.","triggerScenarios":"Returning the response object from a beforeCache hook (mirroring afterResponse's pattern, which is wrong here); returning `true` to mean 'yes cache it'; returning a status code or header value.","commonSituations":"Developers familiar with afterResponse (where you return the response) applying that idiom to beforeCache; refactoring a caching integration; copy-pasting hook shapes across hook types.","solutions":["Mutate the response argument in place (e.g. `response.headers['x'] = 'y'`) and return nothing — implicit undefined is correct.","Return literally `false` if you want to skip caching this response.","Do not return the response, an object, or any truthy non-false value."],"exampleFix":"// before — wrong contract, returns the response\nhooks: { beforeCache: [response => { response.headers.x = '1'; return response; }] }\n\n// after — mutate in place, return nothing\nhooks: { beforeCache: [response => { response.headers.x = '1'; }] }\n\n// or signal 'do not cache'\nhooks: { beforeCache: [response => response.statusCode === 500 ? false : undefined] }","handlingStrategy":"validation","validationCode":"// Wrap beforeCache hooks to enforce the return contract.\nfunction wrapBeforeCache(hook) {\n  return (response) => {\n    const result = hook(response);\n    if (result !== undefined && result !== false) {\n      throw new TypeError('beforeCache hook must return false or undefined — mutate the response directly instead.');\n    }\n    return result;\n  };\n}\noptions.hooks.beforeCache = (options.hooks.beforeCache ?? []).map(wrapBeforeCache);","typeGuard":"type BeforeCacheResult = false | undefined | void;\n\nfunction isValidBeforeCacheReturn(v: unknown): v is BeforeCacheResult {\n  return v === undefined || v === false;\n}","tryCatchPattern":"try {\n  await got(url, options);\n} catch (error) {\n  if (error instanceof TypeError && /beforeCache hook must return false or undefined/.test(error.message)) {\n    throw new Error('beforeCache hook returned an invalid value — mutate response in place, return nothing.', { cause: error });\n  }\n  throw error;\n}","preventionTips":["Mutate the response argument in place; do not return it.","Return literally `false` to skip caching; otherwise return nothing.","Do not copy the afterResponse idiom (which returns the response) into beforeCache."],"tags":["hooks","before-cache","caching","contract-violation","return-value"],"analyzedSha":"e3924aa1e53a6ca3eb93a43618ce532442a89b40","analyzedAt":"2026-08-03T19:22:24.770Z","schemaVersion":2}