microsoft/typescript-go · error · ErrClientError

%w: empty type handle

Error message

%w: empty type handle

What it means

A type-based request passed TypeID 0 ("empty type handle"). TypeID 0 is the zero value and never maps to a registered checker.Type, so resolveTypeHandle rejects it before any registry lookup. It almost always means the client sent a default-initialized field or took the id from a null TypeResponse (type-returning calls return null when there is nothing to report, e.g. getTypeOfSymbol on a symbol with no type).

Source

Thrown at internal/api/session.go:255

	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)
	}
	if projectID == "" {
		return nil, fmt.Errorf("%w: empty project ID for type handle %d", ErrClientError, handle)
	}

	sd.projectRegistriesMu.RLock()
	reg := sd.projectRegistries[projectID]
	sd.projectRegistriesMu.RUnlock()

	if reg == nil {
		return nil, fmt.Errorf("%w: type handle %d not found (no registry for project %s)", ErrClientError, handle, projectID)
	}

	reg.typeRegistryMu.RLock()
	t, ok := reg.typeRegistry[handle]
	reg.typeRegistryMu.RUnlock()

	if !ok {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Null-check type responses before chaining further type queries
  2. Validate the handle is a positive integer before sending; skip or omit the call when it is 0
  3. Model type handles as optional in the client so absence is explicit rather than coerced to 0

Example fix

// before
const t = await call("getTypeOfSymbol", { snapshot, project, symbol });
await call("getTargetOfType", { snapshot, project, type: t?.id ?? 0 }); // empty type handle

// after
const t = await call("getTypeOfSymbol", { snapshot, project, symbol });
if (t) {
  await call("getTargetOfType", { snapshot, project, type: t.id });
}
Defensive patterns

Strategy: type-guard

Validate before calling

// TS client: skip the follow-up when the type handle is empty.
if (!typeId) { // 0/undefined/null
  return;
}
await call("getTargetOfType", { snapshot, project, type: typeId });

Type guard

const isNonEmptyTypeHandle = (id: number | undefined | null): id is number =>
  typeof id === "number" && Number.isInteger(id) && id > 0;

Prevention

When it happens

Trigger: Passing 0 in getSymbolOfType/getTargetOfType/getTypesOfType/getSignaturesOfType params; reading .type?.id of a response where the type was null and letting it default to 0; undefined type field serialized as 0; forwarding a handle before the originating query populated it.

Common situations: Client pipelines that chain type queries without null checks; optional type fields in client structs defaulting to zero; template request objects reused across queries.

Related errors


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