microsoft/typescript-go · error · ErrClientError

invalid AST header offsets: offsets exceed data length (%d)

Error message

invalid AST header offsets: offsets exceed data length (%d)

What it means

One of the four region offsets read from the header (string table, string data, extended data, nodes) points past the end of the buffer. The decoder refuses to slice beyond len(data), so decoding aborts before any node is materialized.

Source

Thrown at internal/api/encoder/decoder.go:71

func newASTDecoder(data []byte) (*astDecoder, error) {
	if len(data) < HeaderSize {
		return nil, fmt.Errorf("data too short for header: %d bytes", len(data))
	}
	version := data[HeaderOffsetMetadata+3]
	if version != ProtocolVersion {
		return nil, fmt.Errorf("unsupported protocol version %d (expected %d)", version, ProtocolVersion)
	}

	strTable := readLE32(data, HeaderOffsetStringOffsets)
	strData := readLE32(data, HeaderOffsetStringData)
	extData := readLE32(data, HeaderOffsetExtendedData)
	nodeOff := readLE32(data, HeaderOffsetNodes)

	dataLen := uint32(len(data))

	// Validate that all offsets are within the buffer.
	if strTable > dataLen || strData > dataLen || extData > dataLen || nodeOff > dataLen {
		return nil, fmt.Errorf("invalid AST header offsets: offsets exceed data length (%d)", dataLen)
	}

	// Validate monotonic non-decreasing order of regions.
	if !(strTable <= strData && strData <= extData && extData <= nodeOff) {
		return nil, fmt.Errorf("invalid AST header offsets: expected strTable <= strData <= extData <= nodeOff (got %d, %d, %d, %d)", strTable, strData, extData, nodeOff)
	}

	d := &astDecoder{
		raw:      data,
		strTable: strTable,
		strData:  strData,
		extData:  extData,
		nodeOff:  nodeOff,
		factory:  ast.NewNodeFactory(ast.NodeFactoryHooks{}),
	}

	d.nodeCount = (len(data) - int(d.nodeOff)) / NodeSize

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Re-encode and compare lengths - a shorter blob identifies the truncating writer
  2. Validate the blob's checksum (the header reserves hash words) before decoding
  3. Write blobs atomically (temp file + rename) to prevent partial writes
  4. If corruption is persistent, re-parse from source to regenerate the AST

Example fix

// before
os.WriteFile(path, blob, 0o644) // partial write possible

// after
tmp := path + ".tmp"
os.WriteFile(tmp, blob, 0o644)
os.Rename(tmp, path) // atomic publish
Defensive patterns

Strategy: validation

Validate before calling

if len(data) < encoder.HeaderSize { return errTooShort }
off := func(i int) uint32 { return binary.LittleEndian.Uint32(data[i*4:]) }
for _, o := range []int{encoder.HeaderOffsetStringOffsets, encoder.HeaderOffsetStringData, encoder.HeaderOffsetExtendedData, encoder.HeaderOffsetNodes} {
	if int(off(o)) > len(data) { return errCorrupt }
}

Prevention

When it happens

Trigger: Corrupted or tail-truncated blob; buffer assembled from mismatched pieces of two encodes; offsets damaged by an endian-confused custom producer.

Common situations: Files truncated by full disks or interrupted writes; caches partially overwritten; network transport dropping bytes when no length prefix is used.

Related errors


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