{"record":{"id":"d36b635aa0e6b7c4","repo":"microsoft/typescript-go","slug":"w-empty-project-id-for-type-handle-d","errorCode":null,"errorMessage":"%w: empty project ID for type handle %d","messagePattern":"%w: empty project ID for type handle (.+?)","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/session.go","lineNumber":258,"sourceCode":"\n\tsd.symbolRegistryMu.RLock()\n\tsymbol, ok := sd.symbolRegistry[handle]\n\tsd.symbolRegistryMu.RUnlock()\n\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: symbol handle %d not found in snapshot registry\", ErrClientError, handle)\n\t}\n\n\treturn symbol, nil\n}\n\n// resolveTypeHandle resolves a type handle within the project's registry.\nfunc (sd *snapshotData) resolveTypeHandle(projectID ProjectID, handle TypeID) (*checker.Type, error) {\n\tif handle == 0 {\n\t\treturn nil, fmt.Errorf(\"%w: empty type handle\", ErrClientError)\n\t}\n\tif projectID == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: empty project ID for type handle %d\", ErrClientError, handle)\n\t}\n\n\tsd.projectRegistriesMu.RLock()\n\treg := sd.projectRegistries[projectID]\n\tsd.projectRegistriesMu.RUnlock()\n\n\tif reg == nil {\n\t\treturn nil, fmt.Errorf(\"%w: type handle %d not found (no registry for project %s)\", ErrClientError, handle, projectID)\n\t}\n\n\treg.typeRegistryMu.RLock()\n\tt, ok := reg.typeRegistry[handle]\n\treg.typeRegistryMu.RUnlock()\n\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: type handle %d not found in project registry\", ErrClientError, handle)\n\t}\n","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/session.go#L240-L276","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nawait call(\"getTargetOfType\", { snapshot, type: typeId }); // empty project ID\n\n// after\nawait call(\"getTargetOfType\", { snapshot, project: typeId.project, type: typeId.id });","handlingStrategy":"validation","validationCode":"// TS client: require project whenever a type handle is sent.\nfunction assertTypeRequest(params: { project?: string; type?: number }) {\n  if (params.type !== undefined) {\n    if (!params.project) throw new Error(\"type handle requires its originating project\");\n    if (!(params.type > 0)) throw new Error(\"type handle must be > 0\");\n  }\n}","typeGuard":"const isProjectScopedTypeHandle = (h: { project?: string; id?: number } | null | undefined): h is { project: string; id: number } =>\n  !!h && typeof h.project === \"string\" && h.project.length > 0 && typeof h.id === \"number\" && h.id > 0;","tryCatchPattern":null,"preventionTips":["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"],"tags":["type-handle","project","validation","required-field","client-error"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}