{"record":{"id":"794dd8c148cc016e","repo":"withastro/astro","slug":"bad-request","errorCode":"BAD_REQUEST","errorMessage":"Failed to serialize request body to JSON. Full error: ${(e as Error).message}","messagePattern":"Failed to serialize request body to JSON\\. Full error: (.+?)","errorType":"exception","errorClass":"ActionError","httpStatus":400,"severity":"error","filePath":"packages/astro/src/actions/runtime/entrypoints/client.ts","lineNumber":53,"sourceCode":"export const getActionPath = createGetActionPath({\n\tbaseUrl: import.meta.env.BASE_URL,\n\tshouldAppendTrailingSlash,\n});\n\nexport const actions = createActionsProxy({\n\thandleAction: async (param, path) => {\n\t\tconst headers = new Headers();\n\t\theaders.set('Accept', 'application/json');\n\t\t// Apply adapter-specific headers for internal fetches\n\t\tfor (const [key, value] of Object.entries(internalFetchHeaders)) {\n\t\t\theaders.set(key, value);\n\t\t}\n\t\tlet body = param;\n\t\tif (!(body instanceof FormData)) {\n\t\t\ttry {\n\t\t\t\tbody = JSON.stringify(param);\n\t\t\t} catch (e) {\n\t\t\t\tthrow new ActionError({\n\t\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t\t\tmessage: `Failed to serialize request body to JSON. Full error: ${(e as Error).message}`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (body) {\n\t\t\t\theaders.set('Content-Type', 'application/json');\n\t\t\t} else {\n\t\t\t\theaders.set('Content-Length', '0');\n\t\t\t}\n\t\t}\n\t\tconst rawResult = await fetch(\n\t\t\tgetActionPathFromString({\n\t\t\t\tbaseUrl: import.meta.env.BASE_URL,\n\t\t\t\tshouldAppendTrailingSlash,\n\t\t\t\tpath: getActionQueryString(path),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tmethod: 'POST',","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/withastro/astro/blob/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/astro/src/actions/runtime/entrypoints/client.ts#L35-L71","documentation":"Before the browser sends an action call that is not FormData, the astro:actions client serializes the argument with JSON.stringify. If the value cannot be serialized — circular references, BigInt, symbols as keys — stringify throws, and the client wraps it as an ActionError with code BAD_REQUEST instead of posting an unusable body.","triggerScenarios":"Calling an action with a circular object (a DOM node graph, an object referencing itself), a BigInt, or class instances whose toJSON fails. Example: actions.save(bigintValue) or passing a parsed graph that contains parent back-references.","commonSituations":"Logging entities with circular references (user.friend = user); sending IDs as BigInt from a database layer; passing class instances (Map/Set are silently lost, BigInt/circular throw) from client components.","solutions":["Convert BigInt to string before the call: String(value) or value.toString()","Break circular references — send a plain DTO, not the live object graph","For files or rich data, submit FormData instead (define the action with accept: 'form' if needed)"],"exampleFix":"// before\nconst id = 12345678901234567890n; // BigInt\nawait actions.saveProfile({ id }); // JSON.stringify throws\n\n// after\nawait actions.saveProfile({ id: id.toString() });","handlingStrategy":"validation","validationCode":"// cheap pre-flight serialization check before calling the action\nfunction assertJsonSerializable(value: unknown): void {\n  JSON.stringify(value, (_k, v) => (typeof v === 'bigint' ? throwSerializationError('BigInt') : v));\n}\nfunction throwSerializationError(t: string): never { throw new Error(`Not JSON serializable: ${t}`); }\nassertJsonSerializable(payload); // throws locally with a clear message\nawait actions.save(payload);","typeGuard":"function isPlainJsonSerializable(v: unknown): boolean {\n  if (v === null || ['string', 'number', 'boolean'].includes(typeof v)) return true;\n  if (typeof v === 'bigint' || typeof v === 'function' || typeof v === 'symbol') return false;\n  if (Array.isArray(v)) return v.every(isPlainJsonSerializable);\n  if (v instanceof Object) {\n    const seen = new WeakSet();\n    // shallow circularity check plus recursion\n    return Object.values(v).every((x) => isPlainJsonSerializable(x));\n  }\n  return false;\n}","tryCatchPattern":"try {\n  await actions.save(payload);\n} catch (e) {\n  if (e instanceof ActionError && e.code === 'BAD_REQUEST' && e.message.includes('serialize')) {\n    // sanitize payload: stringify BigInts, strip circular refs, then retry once\n  } else throw e;\n}","preventionTips":["Convert BigInt to string at the boundary where it is produced","Send DTOs built for transport, not live object graphs with back-references","Use FormData (accept:'form') for anything containing files"],"tags":["astro-actions","json","serialization","bigint","circular-reference","bad-request"],"backgroundTag":"json-stringify-failed","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}