{"record":{"id":"7a71076cfbacea67","repo":"microsoft/typescript-go","slug":"w-type-handle-d-not-found-in-project-registry","errorCode":null,"errorMessage":"%w: type handle %d not found in project registry","messagePattern":"%w: type handle (.+?) not found in project registry","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/session.go","lineNumber":274,"sourceCode":"\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\n\treturn t, nil\n}\n\n// resolveSignatureHandle resolves a signature handle within the project's registry.\nfunc (sd *snapshotData) resolveSignatureHandle(projectID ProjectID, handle SignatureID) (*checker.Signature, error) {\n\tif handle == 0 {\n\t\treturn nil, fmt.Errorf(\"%w: empty signature handle\", ErrClientError)\n\t}\n\tif projectID == \"\" {\n\t\treturn nil, fmt.Errorf(\"%w: empty project ID for signature handle %d\", ErrClientError, handle)\n\t}\n\n\tsd.projectRegistriesMu.RLock()\n\treg := sd.projectRegistries[projectID]\n\tsd.projectRegistriesMu.RUnlock()\n","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/session.go#L256-L292","documentation":"The project's type registry exists but the TypeID is not in it: \"type handle N not found in project registry\". Because type ids are per-checker sequential values, the numeric id can exist in one project's registry and not another's - this error is the signature of a scope mismatch (id from a different project, a different snapshot of the same project, or a stale registry after release/update) rather than a corrupt id.","triggerScenarios":"Type handle from project A sent with project B (same number, different meaning); handle minted in a previous snapshot reused after updateSnapshot; snapshot released and its registry dropped, then queried; id from another session.","commonSituations":"Client-side caches of type ids outliving a snapshot refresh; multi-project editors forwarding a hover type from one project into completion queries for another; background workers processing stale queues after an edit.","solutions":["Re-acquire the type in the current project/snapshot before the follow-up query","Invalidate cached type handles whenever the snapshot id changes or release completes","Bind each cached type id to its exact project and snapshot, and send both verbatim"],"exampleFix":"// before\nawait call(\"getTypeParametersOfType\", { snapshot, project, type: staleType }); // not found in registry\n\n// after\nconst t = await call(\"getTypeAtPosition\", { snapshot, project, file, position });\nif (t) await call(\"getTypeParametersOfType\", { snapshot, project, type: t.id });","handlingStrategy":"fallback","validationCode":"// TS client: only use handles minted in the current snapshot.\nif (cachedType.snapshot !== snapshot || cachedType.project !== project) {\n  cachedType = null; // stale - force re-acquisition\n}\nif (!cachedType) {\n  const t = await call(\"getTypeAtPosition\", { snapshot, project, file, position });\n  if (t) cachedType = { snapshot, project, id: t.id };\n}","typeGuard":"const isLiveTypeHandle = (h: { snapshot: bigint; project: string; id: number }, cur: bigint): h is { snapshot: bigint; project: string; id: number } =>\n  h.snapshot === cur && h.id > 0;","tryCatchPattern":"try {\n  return await call(method, params);\n} catch (e) {\n  if (String(e).includes(\"type handle\") && String(e).includes(\"not found\")) {\n    const t = await call(\"getTypeOfSymbol\", { snapshot: params.snapshot, project: params.project, symbol });\n    if (t) return await call(method, { ...params, type: t.id }); // retry once with fresh handle\n  }\n  throw e;\n}","preventionTips":["Pair every cached type id with its snapshot and project; expire on updateSnapshot/release","Prefer re-querying types at a location over caching ids across edits","Log the (project, snapshot) context whenever this fires to find the mismatched pair quickly"],"tags":["type-handle","registry","project","snapshot","stale-handle","client-error"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}