{"record":{"id":"86c108d8559648a5","repo":"toon-format/toon","slug":"unexpected-content-after-the-document-root","errorCode":null,"errorMessage":"Unexpected content after the document root","messagePattern":"Unexpected content after the document root","errorType":"exception","errorClass":"ToonDecodeError","httpStatus":null,"severity":"error","filePath":"packages/toon/src/decode/decoders.ts","lineNumber":173,"sourceCode":"    { line: line.lineNumber, source: line.raw },\n  )\n}\n\nfunction keylessFieldsHeaderError(line: ParsedLine): ToonDecodeError {\n  return new ToonDecodeError(\n    'Keyless header with a field list is only valid at the document root',\n    { line: line.lineNumber, source: line.raw },\n  )\n}\n\n// Strict decoding never silently discards input, so a line after the root form is an error.\nfunction* assertFullyConsumed(reader: LineReader, strict: boolean): LineRule {\n  if (!strict) {\n    return\n  }\n  const line = yield* peekLine(reader)\n  if (line) {\n    throw new ToonDecodeError(\n      'Unexpected content after the document root',\n      { line: line.lineNumber, source: line.raw },\n    )\n  }\n}\n\nfunction assertNoDuplicateKey(key: string, line: ParsedLine, seenKeys: Set<string> | undefined): void {\n  if (!seenKeys)\n    return\n  if (seenKeys.has(key)) {\n    throw new ToonDecodeError(\n      `Duplicate sibling key \"${key}\"`,\n      { line: line.lineNumber, source: line.raw },\n    )\n  }\n  seenKeys.add(key)\n}\n","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/toon-format/toon/blob/604eac266e35166bed6f5b9e3bd586e9c60a9330/packages/toon/src/decode/decoders.ts#L155-L191","documentation":"In strict mode, decodeDocument requires the entire input to be consumed by the root value; any non-empty line remaining after the root has been decoded triggers this error. This guards against concatenated documents or trailing garbage that would otherwise be silently ignored.","triggerScenarios":"Calling decode with strict:true on input that contains a second top-level block after the root key-value/array, or any trailing non-blank lines.","commonSituations":"Concatenating two TOON documents into one file; log-style appending of extra records; stray trailing line from a bad copy-paste or a diff artifact.","solutions":["Remove the extra content after the root","Split the file into separate documents and decode each individually","Decode with strict:false if trailing content should be tolerated","Wrap multiple records under one root array header"],"exampleFix":"// before\nname: Ada\nextra: orphan\n\n// after\nname: Ada","handlingStrategy":"validation","validationCode":"function isSingleDocument(text: string): boolean {\n  const lines = text.split('\\n').map(l => l.trim()).filter(Boolean)\n  return lines.length > 0 && !lines.slice(1).some(l => !l.startsWith('- ') && /^[^\\s[][^:]*:/.test(l) === false ? false : false) // rely on decode strict for trailing check\n}\n// simpler pre-check: ensure only one top-level key block by decoding strict:false first and checking reader leftovers via strict decode","typeGuard":null,"tryCatchPattern":"try {\n  return decode(input, { strict: true })\n} catch (e) {\n  if (e instanceof ToonDecodeError && e.message.includes('after the document root')) {\n    return input.split(/\\n(?=\\S)/).map(part => decode(part)) // split concatenated docs\n  }\n  throw e\n}","preventionTips":["Never concatenate TOON documents; use one root or separate files","Strip trailing blank/garbage lines before decoding","Use strict:false only when trailing content is expected"],"tags":["toon","decoder","strict-mode","trailing-content"],"backgroundTag":"unexpected-trailing-content","analyzedSha":"604eac266e35166bed6f5b9e3bd586e9c60a9330","analyzedAt":"2026-08-31T11:09:25.043Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}