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
- Always send the project the type handle came from - capture both fields from the originating TypeResponse
- Store handles as a pair {projectId, typeId} in client state so one is never sent without the other
- 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
- Always carry type handles as {project, id} pairs in client state
- Take the project from the response that produced the type
- Remember the asymmetry: symbols are snapshot-scoped, types/signatures are project-scoped
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
- %w: empty project ID for signature handle %d
- %w: empty type handle
- %w: type handle %d not found (no registry for project %s)
- %w: type handle %d not found in project registry
- %w: project has no program
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/d36b635aa0e6b7c4.
Report an issue: GitHub.