{"record":{"id":"d54554d34f8539a7","repo":"microsoft/typescript-go","slug":"no-nodes-to-decode","errorCode":null,"errorMessage":"no nodes to decode","messagePattern":"no nodes to decode","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/encoder/decoder.go","lineNumber":140,"sourceCode":"\tif i+1 >= d.nodeCount {\n\t\treturn d.childBuf\n\t}\n\tfirstChild := i + 1\n\tif d.nodeField(firstChild, NodeOffsetParent) != uint32(i) {\n\t\treturn d.childBuf\n\t}\n\td.childBuf = append(d.childBuf, firstChild)\n\tnext := int(d.nodeField(firstChild, NodeOffsetNext))\n\tfor next != 0 {\n\t\td.childBuf = append(d.childBuf, next)\n\t\tnext = int(d.nodeField(next, NodeOffsetNext))\n\t}\n\treturn d.childBuf\n}\n\nfunc (d *astDecoder) decode() (*ast.Node, error) {\n\tif d.nodeCount < 2 {\n\t\treturn nil, errors.New(\"no nodes to decode\")\n\t}\n\n\td.nodes = make([]*ast.Node, d.nodeCount)\n\td.nodeLists = make([]*ast.NodeList, d.nodeCount)\n\t// Pre-allocate arena for NodeList child slices. Each node can appear as a\n\t// child at most once, so nodeCount is an upper bound on total child pointers.\n\td.nodeArena = make([]*ast.Node, 0, d.nodeCount)\n\n\t// Process bottom-up so children exist before parents.\n\tfor i := d.nodeCount - 1; i >= 1; i-- {\n\t\tkind := d.nodeField(i, NodeOffsetKind)\n\t\tpos := d.nodeField(i, NodeOffsetPos)\n\t\tend := d.nodeField(i, NodeOffsetEnd)\n\t\tdata := d.nodeField(i, NodeOffsetData)\n\t\tchildIndices := d.collectChildren(i)\n\n\t\tif kind == SyntaxKindNodeList {\n\t\t\tchildNodes := d.allocNodeSlice(len(childIndices))","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/encoder/decoder.go#L122-L158","documentation":"After the header, nodeCount is computed as (len(data)-nodeOff)/NodeSize; a count below 2 means no root exists (index 0 is reserved, the root lives at index 1). Valid encodings always contain at least the SourceFile root plus an EndOfFile token, so this is an empty or degenerate node region.","triggerScenarios":"A blob containing only the header with no node region; nodeOff == len(data); truncation that removes the node region while leaving header checks green.","commonSituations":"Header-only writes from a failed encode; slicing off the node region by accident; caches storing only the string table portion.","solutions":["Ensure the blob came from a complete Encode call, not a partial/aborted write","Re-encode from the parsed SourceFile","Validate (len(data)-nodeOff)/NodeSize >= 2 before decoding untrusted blobs","Check that nothing stripped the tail of the buffer (compare against the encoder's reported length)"],"exampleFix":"// before\ndecoder.DecodeSourceFile(trimmedBlob)\n\n// after\nif (len(blob)-int(nodeOff))/encoder.NodeSize < 2 {\n\treturn errors.New(\"empty AST blob\")\n}","handlingStrategy":"validation","validationCode":"nodeOff := int(binary.LittleEndian.Uint32(data[encoder.HeaderOffsetNodes:]))\nif (len(data)-nodeOff)/encoder.NodeSize < 2 {\n\treturn errors.New(\"encoded AST has no nodes\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure encodes complete before publishing blobs","Verify the node region exists when handling untrusted buffers","Compare stored blob size with the encoder's reported size"],"tags":["go","encoder","binary","empty-data","truncation"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}