microsoft/typescript-go · error · ErrClientError

%w: empty project ID for type handle %d

Error message

%w: empty project ID for type handle %d

What it means

A type-based request supplied a TypeID but left the ProjectID empty, so resolveTypeHandle fails with "empty project ID for type handle N". Type ids are sequential per checker (per project), not global: the same numeric TypeID can denote different types in different projects, which is why every type-handle resolution is project-scoped and the project parameter is mandatory whenever a type handle is sent.

Source

Thrown at internal/api/session.go:258

	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 {
		return nil, fmt.Errorf("%w: type handle %d not found in project registry", ErrClientError, handle)
	}

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Always send the project the type handle came from - capture both fields from the originating TypeResponse
  2. Store handles as a pair {projectId, typeId} in client state so one is never sent without the other
  3. If project context is unknown, default to the SymbolResponse.Project field that accompanied the symbol the type was derived from

Example fix

// before
await call("getTargetOfType", { snapshot, type: typeId }); // empty project ID

// after
await call("getTargetOfType", { snapshot, project: typeId.project, type: typeId.id });
Defensive patterns

Strategy: validation

Validate before calling

// TS client: require project whenever a type handle is sent.
function assertTypeRequest(params: { project?: string; type?: number }) {
  if (params.type !== undefined) {
    if (!params.project) throw new Error("type handle requires its originating project");
    if (!(params.type > 0)) throw new Error("type handle must be > 0");
  }
}

Type guard

const isProjectScopedTypeHandle = (h: { project?: string; id?: number } | null | undefined): h is { project: string; id: number } =>
  !!h && typeof h.project === "string" && h.project.length > 0 && typeof h.id === "number" && h.id > 0;

Prevention

When it happens

Trigger: Omitting the project field in params for getSymbolOfType, getTargetOfType, getTypeParametersOfType, getSignaturesOfType, etc.; a client struct where project defaults to ""; copying only the type id out of a response and dropping the project it belonged to.

Common situations: Client helpers that pass handles without their originating project context; refactors that drop the project field; assuming types are identified globally like symbols (they are not - symbols are snapshot-wide, types are per-project).

Related errors


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