{"record":{"id":"02a2dc681597fbf1","repo":"agalwood/Motrix","slug":"plugin-metadata-value-not-serializable","errorCode":"plugin.metadata.value_not_serializable","errorMessage":"plugin.metadata.value_not_serializable: undefined is not JSON-serializable","messagePattern":"plugin\\.metadata\\.value_not_serializable: undefined is not JSON-serializable","errorType":"validation","errorClass":"MetadataError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/capabilities/metadata.ts","lineNumber":68,"sourceCode":"  }\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_QUOTA = 64 << 10 // 64 KB\n\n/**\n * Serialize `value` to JSON.\n * Throws `plugin.metadata.value_not_serializable` when:\n *  - the value is top-level `undefined`\n *  - JSON.stringify returns undefined (e.g. a plain function)\n *  - JSON.stringify throws (e.g. BigInt, class with throwing toJSON)\n */\nfunction serialize(value: unknown): string {\n  if (value === undefined) {\n    throw new MetadataError(\n      'plugin.metadata.value_not_serializable',\n      'plugin.metadata.value_not_serializable: undefined is not JSON-serializable'\n    )\n  }\n  let json: string | undefined\n  try {\n    json = JSON.stringify(value)\n  } catch {\n    throw new MetadataError(\n      'plugin.metadata.value_not_serializable',\n      'plugin.metadata.value_not_serializable: value cannot be serialized to JSON'\n    )\n  }\n  if (json === undefined) {\n    throw new MetadataError(\n      'plugin.metadata.value_not_serializable',\n      'plugin.metadata.value_not_serializable: value cannot be serialized to JSON'\n    )","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/capabilities/metadata.ts#L50-L86","documentation":"The top-level value passed to the metadata set() path is strictly undefined. JSON.stringify would silently drop a top-level undefined (returning undefined), so serialize() short-circuits to an explicit, message-bearing error rather than storing nothing. This is the value===undefined branch, distinct from 113 (stringify threw) and 114 (stringify returned undefined).","triggerScenarios":"Calling metadata.set(taskId, pluginId, key, undefined); a variable that was conditionally assigned and ended up undefined; reading a missing config field and passing it straight through.","commonSituations":"Plugin stores the result of an optional lookup without checking; refactor left a placeholder; TypeScript optional property accessed without nullish-coalescing.","solutions":["Use null (or an explicit empty marker) instead of undefined to represent absence.","Skip the set() call entirely when the value is undefined.","Default the value at the source with ?? null or ?? {}."],"exampleFix":"// before\nawait metadata.set(taskId, pluginId, key, maybeValue)\n\n// after\nif (maybeValue !== undefined) await metadata.set(taskId, pluginId, key, maybeValue)","handlingStrategy":"validation","validationCode":"if (value === undefined) throw new Error('metadata value required')\nawait metadata.set(taskId, pluginId, key, value)","typeGuard":"function isDefined<T>(v: T | undefined): v is T { return v !== undefined }","tryCatchPattern":null,"preventionTips":["Represent absence with null, not undefined, in stored values.","Skip the set() call when the value is undefined instead of storing a marker.","Constrain settable values to a JSONValue type so the compiler flags undefined."],"tags":["metadata","serialization","typescript","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}