{"record":{"id":"c68dc2f63a640735","repo":"janhq/jan","slug":"failed-to-parse-request-body-as-json-e-instanceof-error-e","errorCode":null,"errorMessage":"Failed to parse request body as JSON: ${e instanceof Error ? e.message : String(e)}","messagePattern":"Failed to parse request body as JSON: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/lib/model-factory.ts","lineNumber":525,"sourceCode":"    // set max_output_tokens = 0 in assistant params mean \"no cap\", not\n    // \"produce zero tokens\" — coerce here, gated to llamacpp only because\n    // OpenAI/Anthropic reject negative values.\n    if (keepLlamacppOnly && merged.max_tokens === 0) {\n      merged.max_tokens = -1\n    }\n    decodeAudioSentinelsInBody(merged)\n    decodeVideoSentinelsInBody(merged)\n    decodeToolImageSentinelsInBody(merged)\n    return merged\n  }\n\n  return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n    let rawBody: Record<string, unknown> | null = null\n    if (init?.method === 'POST' || !init?.method) {\n      try {\n        rawBody = init?.body ? JSON.parse(init.body as string) : {}\n      } catch (e) {\n        throw new Error(\n          `Failed to parse request body as JSON: ${e instanceof Error ? e.message : String(e)}`\n        )\n      }\n      init = { ...init, body: JSON.stringify(buildBody(rawBody!, true)) }\n    }\n\n    let res: Response\n    try {\n      res = await baseFetch(input, init)\n    } catch (err) {\n      const friendly = describeTransportError(err)\n      if (!friendly) throw err\n      throw new Error(`${friendly} (${requestUrlOf(input)})`)\n    }\n    if (res.ok) {\n      // OpenAI-compatible servers may interleave custom named SSE events (e.g.\n      // tool-progress) with chat.completion.chunk data; the AI SDK validates\n      // every data line against the chunk schema, so strip non-default events.","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/lib/model-factory.ts#L507-L543","documentation":"Thrown inside createCustomFetch's wrapped fetch when a POST request body cannot be JSON.parse'd before it is rebuilt via buildBody. The wrapper needs to parse, transform, and re-serialize the body, so a non-JSON body (form data, binary, malformed JSON) is a hard failure with the underlying parse error appended.","triggerScenarios":"Calling the wrapped fetch with init.body set to a FormData, Blob, URLSearchParams, or ReadableStream object; passing a hand-built string that is not valid JSON; encoding bugs that emit truncated JSON.","commonSituations":"Swapping in a custom fetch for file uploads that send multipart bodies; injecting a raw string body with typos; middleware ordering where another wrapper already replaced the body with a stream.","solutions":["Ensure POST bodies are plain JSON-serializable objects passed through JSON.stringify before calling fetch.","Skip the JSON path for uploads: set init.method explicitly and only pass bodies your custom fetch can parse, or use a fetch wrapper that bypasses this transform.","Log init.body (and its typeof) when the error occurs to find which call site sends non-JSON bodies."],"exampleFix":"// before\nfetch(url, { method: 'POST', body: formData })\n// after\nfetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) })","handlingStrategy":"validation","validationCode":"if (typeof body !== 'string' || body.trim()[0] !== '{') throw new TypeError('POST body must be a JSON string')\nJSON.parse(body) // dry-run parse before calling fetch","typeGuard":"const isJsonString = (b) => typeof b === 'string' && (() => { try { JSON.parse(b); return true } catch { return false } })()","tryCatchPattern":"try { await wrappedFetch(url, init) } catch (e) { if (String(e.message).startsWith('Failed to parse request body as JSON')) { console.error('body was:', typeof init.body, init.body); } else throw e }","preventionTips":["Always build bodies with JSON.stringify of a plain object.","Never pass FormData/Blob through a custom fetch that parses the body.","Unit-test custom fetch wrappers with the exact body shapes you send."],"tags":["fetch","json","request-body","serialization"],"backgroundTag":"json-parse-error","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}