microsoft/typescript-go · error · ErrClientError

%w: empty symbol handle

Error message

%w: empty symbol handle

What it means

A symbol-based request passed SymbolID 0 ("empty symbol handle"). 0 is the zero value of SymbolID and is reserved: no symbol ever registers under it, so resolveSymbolHandle rejects it immediately as a client error. In practice 0 arrives from default-initialized params or from the id of a null SymbolResponse - when the server finds no symbol (e.g. getSymbolAtPosition on whitespace or a comment) it returns null, and reading .id of that yields 0/undefined.

Source

Thrown at internal/api/session.go:238

	reg := sd.getOrCreateProjectRegistry(projectID)
	reg.typeRegistryMu.Lock()
	defer reg.typeRegistryMu.Unlock()
	existing := reg.typeRegistry[id]

	if existing != nil {
		if existing != t {
			panic("duplicate type")
		}
		return id
	}
	reg.typeRegistry[id] = t
	return id
}

// resolveSymbolHandle resolves a symbol handle within the snapshot's registry.
func (sd *snapshotData) resolveSymbolHandle(handle SymbolID) (*ast.Symbol, error) {
	if handle == 0 {
		return nil, fmt.Errorf("%w: empty symbol handle", ErrClientError)
	}

	sd.symbolRegistryMu.RLock()
	symbol, ok := sd.symbolRegistry[handle]
	sd.symbolRegistryMu.RUnlock()

	if !ok {
		return nil, fmt.Errorf("%w: symbol handle %d not found in snapshot registry", ErrClientError, handle)
	}

	return symbol, nil
}

// resolveTypeHandle resolves a type handle within the project's registry.
func (sd *snapshotData) resolveTypeHandle(projectID ProjectID, handle TypeID) (*checker.Type, error) {
	if handle == 0 {
		return nil, fmt.Errorf("%w: empty type handle", ErrClientError)
	}

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Null-check symbol responses: only issue symbol-follow-up calls when the response object exists
  2. Guard every handle before sending: reject/omit the call when symbolId is 0, null, or undefined
  3. Treat 0 as 'absent' in your client model (make the field optional, not defaulted)

Example fix

// before
const sym = await call("getSymbolAtPosition", { snapshot, project, file, position });
await call("getTypeOfSymbol", { snapshot, project, symbol: sym?.id ?? 0 }); // empty symbol handle

// after
const sym = await call("getSymbolAtPosition", { snapshot, project, file, position });
if (sym) {
  await call("getTypeOfSymbol", { snapshot, project, symbol: sym.id });
}
Defensive patterns

Strategy: type-guard

Validate before calling

// TS client: check the handle before the request.
if (!symbolId) { // covers 0, undefined, null
  return; // nothing found earlier - skip the follow-up call
}
await call("getTypeOfSymbol", { snapshot, project, symbol: symbolId });

Type guard

const isNonEmptySymbolHandle = (id: number | bigint | undefined | null): id is number =>
  (typeof id === "number" || typeof id === "bigint") && id !== 0 && id > 0;

Prevention

When it happens

Trigger: Passing a zero-valued symbol field in getTypeOfSymbol/getMembersOfSymbol/getParentOfSymbol params; reusing the id from a getSymbolAtPosition response that was null; JS undefined id coerced to 0 in a struct; forwarding symbol handles before the first symbol query populated them.

Common situations: Client skips the null check on symbol lookups at whitespace/keywords; optional-field handling that defaults handles to 0 instead of omitting the request; copy-pasting a follow-up query template without filling the symbol.

Related errors


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