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
- Re-encode and compare lengths - a shorter blob identifies the truncating writer
- Validate the blob's checksum (the header reserves hash words) before decoding
- Write blobs atomically (temp file + rename) to prevent partial writes
- 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
- Write encoded blobs atomically (temp + rename)
- Checksum cached blobs and validate before decode
- Never hand-edit or append to encoded files
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
- invalid AST header offsets: expected strTable <= strData <=
- data too short for header: %d bytes
- no nodes to decode
- expected SourceFile root, got %v
- unsupported protocol version %d (expected %d)
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/ec270604fefbaea0.
Report an issue: GitHub.