microsoft/typescript-go · error · ErrClientError
invalid AST header offsets: expected strTable <= strData <=
Error message
invalid AST header offsets: expected strTable <= strData <= extData <= nodeOff (got %d, %d, %d, %d)
What it means
The four region offsets are individually in-bounds but violate the required layout strTable <= strData <= extData <= nodeOff. The format mandates contiguous ordered regions, so violation means the header bytes are effectively garbage rather than a decoder bug.
Source
Thrown at internal/api/encoder/decoder.go:76
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
// Convert entire string data region to a single Go string upfront.
// Substringing a Go string shares the backing array, so subsequent
// getString calls produce substrings with zero allocations.
d.allStringData = string(data[d.strData:])
View on GitHub (pinned to 1bcfa18d79)
Solutions
- Confirm you opened the artifact actually written by the encoder, not a same-named file of another format
- Regenerate the blob from the parsed source file
- If fuzzing, treat as invalid input and skip - both header checks exist to reject this safely
- Add a magic/version prefix to your cache entries so wrong-format files fail earlier
Example fix
// before
blob, _ := os.ReadFile(cachePath) // may be any file
// after
blob, _ := os.ReadFile(cachePath)
if len(blob) < 4 || string(blob[:4]) != "tsast" { return errors.New("not an AST blob") } Defensive patterns
Strategy: validation
Validate before calling
o1 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetStringOffsets:])
o2 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetStringData:])
o3 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetExtendedData:])
o4 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetNodes:])
if !(o1 <= o2 && o2 <= o3 && o3 <= o4) { return errNotASTBlob } Prevention
- Tag cache entries with a magic prefix so wrong-format files fail earlier
- Never feed files of unknown provenance to the decoder
- Regenerate suspect blobs from source instead of patching them
When it happens
Trigger: Random or text bytes that pass the length check; a blob whose header alone was overwritten; endianness mistakes in a non-reference producer writing big-endian offsets.
Common situations: Decoding a file that was never an encoded AST (config, log) due to a path mix-up; memory corruption in fuzzing harnesses; byte-swapped copies through tools that assume big-endian.
Related errors
- invalid AST header offsets: offsets exceed data length (%d)
- expected SourceFile root, got %v
- data too short for header: %d bytes
- unsupported protocol version %d (expected %d)
- no nodes to decode
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/2942521d4a7a52c8.
Report an issue: GitHub.