microsoft/typescript-go · error · ErrClientError
at node %d (kind %v): %w
Error message
at node %d (kind %v): %w
What it means
Wrapper error from the bottom-up node reconstruction loop: node i (index and syntax kind are printed) failed in createNode. The wrapped error comes from the generated tables - 'unknown string node kind', 'unknown extended data node kind', or 'unhandled node kind with N children' - meaning the kind/data bytes map to no constructor this build knows.
Source
Thrown at internal/api/encoder/decoder.go:172
data := d.nodeField(i, NodeOffsetData)
childIndices := d.collectChildren(i)
if kind == SyntaxKindNodeList {
childNodes := d.allocNodeSlice(len(childIndices))
for _, ci := range childIndices {
if d.nodes[ci] != nil {
childNodes = append(childNodes, d.nodes[ci])
}
}
nl := d.factory.NewNodeList(childNodes)
nl.Loc = core.NewTextRange(int(pos), int(end))
d.nodeLists[i] = nl
continue
}
node, err := d.createNode(ast.Kind(kind), data, childIndices)
if err != nil {
return nil, fmt.Errorf("at node %d (kind %v): %w", i, ast.Kind(kind), err)
}
node.Loc = core.NewTextRange(int(pos), int(end))
node.Flags = ast.NodeFlags(d.nodeField(i, NodeOffsetFlags))
d.nodes[i] = node
}
return d.nodes[1], nil
}
// getModifierList creates a *ast.ModifierList from a child index that is a NodeList.
func (d *astDecoder) getModifierList(ci int) *ast.ModifierList {
nl := d.nodeLists[ci]
if nl == nil {
return nil
}
ml := d.factory.NewModifierList(nl.Nodes)
ml.Loc = nl.Loc
return mlView on GitHub (pinned to 1bcfa18d79)
Solutions
- Pin encoder and decoder to the exact same typescript-go build and re-encode
- Regenerate stored fixtures/caches after any upgrade
- Use the printed node index and kind diagnostically: one wildly large kind means corruption; many failures across kinds means version skew
- Add integrity verification before decoding blobs from untrusted sources
Example fix
// before client := newClient(serverV6) // encodes with newer kind table server := newServer(libV5) // decodes with older table // after client := newClient(libV5) server := newServer(libV5) // same build on both ends
Defensive patterns
Strategy: try-catch
Try / catch
sf, err := encoder.DecodeSourceFile(data)
if err != nil {
var idx int; var kind ast.Kind
if _, e := fmt.Sscanf(err.Error(), "at node %d (kind %v)", &idx, &kind); e == nil {
log.Printf("blob incompatible or corrupt at node %d (kind %v); re-encoding", idx, kind)
return reparseAndEncode(source)
}
return nil, err
} Prevention
- Pin both sides of an AST exchange to the same library build
- Regenerate caches/fixtures after upgrades
- Verify checksums before decoding untrusted blobs
When it happens
Trigger: Version skew where the encoder emitted a syntax kind or data-type tag this decoder's generated tables don't cover; bit-flipped kind fields in a corrupted blob; truncation making the last node's fields read as garbage.
Common situations: Upgrading one side of a pair that exchanges encoded ASTs; long-lived caches surviving an upgrade; fuzzed or checksum-less transport of blobs.
Related errors
- expected SourceFile root, got %v
- unsupported protocol version %d (expected %d)
- invalid AST header offsets: offsets exceed data length (%d)
- invalid AST header offsets: expected strTable <= strData <=
- no nodes to decode
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/10fb19a6d85bb754.
Report an issue: GitHub.