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

  1. Re-encode the AST with the current build (re-parse the source file)
  2. Pin encoder and decoder to the same typescript-go version on both ends of the exchange
  3. Version-stamp cache entries and invalidate them on library upgrade
  4. 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

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


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