microsoft/typescript-go · error · ErrClientError
no nodes to decode
Error message
no nodes to decode
What it means
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.
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)
Example fix
// before
decoder.DecodeSourceFile(trimmedBlob)
// after
if (len(blob)-int(nodeOff))/encoder.NodeSize < 2 {
return errors.New("empty AST blob")
} Defensive patterns
Strategy: validation
Validate before calling
nodeOff := int(binary.LittleEndian.Uint32(data[encoder.HeaderOffsetNodes:]))
if (len(data)-nodeOff)/encoder.NodeSize < 2 {
return errors.New("encoded AST has no nodes")
} Prevention
- 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
When it happens
Trigger: A blob containing only the header with no node region; nodeOff == len(data); truncation that removes the node region while leaving header checks green.
Common situations: Header-only writes from a failed encode; slicing off the node region by accident; caches storing only the string table portion.
Related errors
- data too short for header
- expected SourceFile root, got
- invalid AST header offsets: offsets exceed data length
- unsupported protocol version
- at node (kind )
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/d54554d34f8539a7.
Report an issue: GitHub.
Appendix: source
Thrown at internal/api/encoder/decoder.go:140
if i+1 >= d.nodeCount {
return d.childBuf
}
firstChild := i + 1
if d.nodeField(firstChild, NodeOffsetParent) != uint32(i) {
return d.childBuf
}
d.childBuf = append(d.childBuf, firstChild)
next := int(d.nodeField(firstChild, NodeOffsetNext))
for next != 0 {
d.childBuf = append(d.childBuf, next)
next = int(d.nodeField(next, NodeOffsetNext))
}
return d.childBuf
}
func (d *astDecoder) decode() (*ast.Node, error) {
if d.nodeCount < 2 {
return nil, errors.New("no nodes to decode")
}
d.nodes = make([]*ast.Node, d.nodeCount)
d.nodeLists = make([]*ast.NodeList, d.nodeCount)
// Pre-allocate arena for NodeList child slices. Each node can appear as a
// child at most once, so nodeCount is an upper bound on total child pointers.
d.nodeArena = make([]*ast.Node, 0, d.nodeCount)
// Process bottom-up so children exist before parents.
for i := d.nodeCount - 1; i >= 1; i-- {
kind := d.nodeField(i, NodeOffsetKind)
pos := d.nodeField(i, NodeOffsetPos)
end := d.nodeField(i, NodeOffsetEnd)
data := d.nodeField(i, NodeOffsetData)
childIndices := d.collectChildren(i)
if kind == SyntaxKindNodeList {
childNodes := d.allocNodeSlice(len(childIndices))View on GitHub (pinned to 1bcfa18d79)