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.

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)

Solutions

  1. Ensure the blob came from a complete Encode call, not a partial/aborted write
  2. Re-encode from the parsed SourceFile
  3. Validate (len(data)-nodeOff)/NodeSize >= 2 before decoding untrusted blobs
  4. 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

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


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/d54554d34f8539a7. Report an issue: GitHub.