microsoft/typescript-go · error · ErrClientError
unsupported protocol version %d (expected %d)
Error message
unsupported protocol version %d (expected %d)
What it means
Byte 3 of the buffer (the high byte of the metadata header word) carries the encoder protocol version and must equal encoder.ProtocolVersion. A mismatch means the blob was written by a different, binary-incompatible build of the AST encoder; the layout cannot be trusted.
Source
Thrown at internal/api/encoder/decoder.go:59
return node.AsSourceFile(), nil
}
// DecodeNodes decodes binary-encoded AST data into a tree of *ast.Node objects.
func DecodeNodes(data []byte) (*ast.Node, error) {
d, err := newASTDecoder(data)
if err != nil {
return nil, err
}
return d.decode()
}
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)
}View on GitHub (pinned to 1bcfa18d79)
Solutions
- Re-encode the AST with the current build (re-parse the source file)
- Pin encoder and decoder to the same typescript-go version on both ends of the exchange
- Version-stamp cache entries and invalidate them on library upgrade
- Regenerate binary fixtures with the new encoder after upgrading
Example fix
// before
cacheKey := fmt.Sprintf("ast:%s", fileName) // survives upgrades badly
// after
cacheKey := fmt.Sprintf("ast:v%d:%s", encoder.ProtocolVersion, fileName) Defensive patterns
Strategy: validation
Validate before calling
if len(data) > encoder.HeaderOffsetMetadata+3 &&
data[encoder.HeaderOffsetMetadata+3] != encoder.ProtocolVersion {
// stale blob: re-encode instead of decode
return reparseAndEncode(source)
}
return encoder.DecodeSourceFile(data) Prevention
- Pin encoder and decoder to the same typescript-go build
- Include ProtocolVersion in cache keys
- Regenerate binary fixtures after upgrades
When it happens
Trigger: Decoding with an older or newer typescript-go build than the one that encoded the AST; blobs persisted to disk or cache across an upgrade; hand-crafted buffers with a wrong metadata word.
Common situations: Server upgraded while a client or cache still holds old blobs; two copies of the library with different versions in one process; CI replaying recorded fixtures after a version bump.
Related errors
- expected SourceFile root, got %v
- no nodes to decode
- data too short for header: %d bytes
- invalid AST header offsets: offsets exceed data length (%d)
- invalid AST header offsets: expected strTable <= strData <=
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/3964c676272fac5e.
Report an issue: GitHub.