microsoft/typescript-go · error

Invalid node handle: ${handle}

Error message

Invalid node handle: ${handle}

What it means

Thrown by parseNodeHandle when the handle string contains no '.' at all. Node handles have the format "index.kind.path" (path may itself contain dots); the first indexOf('.') failing means the input is a single segment - e.g., a bare numeric id, a filename, or some other identifier that is not a handle.

Source

Thrown at _packages/native-preview/src/api/node/node.ts:328

}

/**
 * Parsed components of a node handle.
 */
export interface ParsedNodeHandle {
    index: number;
    kind: SyntaxKind;
    path: Path;
}

/**
 * Parse a node handle string into its components.
 * Handle format: "index.kind.path" where path may contain dots.
 */
export function parseNodeHandle(handle: string): ParsedNodeHandle {
    const firstDot = handle.indexOf(".");
    if (firstDot === -1) {
        throw new Error(`Invalid node handle: ${handle}`);
    }
    const secondDot = handle.indexOf(".", firstDot + 1);
    if (secondDot === -1) {
        throw new Error(`Invalid node handle: ${handle}`);
    }

    return {
        index: parseInt(handle.slice(0, firstDot), 10),
        kind: parseInt(handle.slice(firstDot + 1, secondDot), 10) as SyntaxKind,
        path: handle.slice(secondDot + 1) as Path,
    };
}

/**
 * Decode binary-encoded AST data into a Node.
 * Works for any binary-encoded node, including synthetic nodes
 * (e.g. from typeToTypeNode) that don't have a source file.
 */

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Only pass handles obtained from RemoteNode.id / API responses - never construct them by hand
  2. Validate the shape first (must contain at least two '.') and reject early with your own error message
  3. If you only have an index, use the appropriate API (node lookup by index) instead of forging a handle
  4. Check for truncation where the handle was stored/transported and its tail was cut off

Example fix

// before
const parsed = parseNodeHandle(userInput); // e.g. '42' or 'main.ts' -> throws

// after
const NODE_HANDLE_RE = /^\d+\.\d+\..+$/;
if (!NODE_HANDLE_RE.test(userInput)) {
    throw new TypeError(`Not a node handle: ${userInput}`);
}
const parsed = parseNodeHandle(userInput);
Defensive patterns

Strategy: type-guard

Validate before calling

// Require the full 'index.kind.path' shape before parsing
const NODE_HANDLE_RE = /^\d+\.\d+\..+$/;
if (!NODE_HANDLE_RE.test(handle)) {
    throw new TypeError(`Not a node handle (expected 'index.kind.path'): ${handle}`);
}
const parsed = parseNodeHandle(handle);

Type guard

const isNodeHandle = (s: string): boolean => /^\d+\.\d+\..+$/.test(s);

Try / catch

try {
    parsed = parseNodeHandle(value);
} catch (e) {
    if (e instanceof Error && e.message.startsWith('Invalid node handle')) {
        // wrong kind of identifier passed: fail with context instead of propagating
        throw new TypeError(`Expected a node handle from RemoteNode.id, got: ${value}`);
    }
    throw e;
}

Prevention

When it happens

Trigger: Passing a raw file path, symbol name, numeric node index, or arbitrary user string where a node handle is expected; handles truncated to their first segment; strings sourced from a different tool with its own id format.

Common situations: Plumbing tsgo API ids (symbol ids, project ids) into functions that expect node handles; debug tooling that reconstructs handles by string concatenation and drops segments; deserialization bugs stripping dotted suffixes.

Related errors


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