{"record":{"id":"feacc2f37d9e82ca","repo":"microsoft/typescript-go","slug":"w-project-s-not-found","errorCode":null,"errorMessage":"%w: project %s not found","messagePattern":"%w: project (.+?) not found","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/session.go","lineNumber":97,"sourceCode":"\tproj, err := sd.getProject(projectHandle)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprogram := proj.GetProgram()\n\tif program == nil {\n\t\treturn nil, fmt.Errorf(\"%w: project has no program\", ErrClientError)\n\t}\n\n\treturn program, nil\n}\n\n// getProject looks up a project from a project handle within this snapshot.\nfunc (sd *snapshotData) getProject(projectHandle ProjectID) (*project.Project, error) {\n\tprojectName := parseProjectHandle(projectHandle)\n\tproj := sd.snapshot.ProjectCollection.GetProjectByPath(projectName)\n\tif proj == nil {\n\t\treturn nil, fmt.Errorf(\"%w: project %s not found\", ErrClientError, projectName)\n\t}\n\treturn proj, nil\n}\n\n// nodeHandleFrom creates an index-based node handle (index.kind.path), building a node index table\n// for the file on-demand if needed.\nfunc (sd *snapshotData) nodeHandleFrom(node *ast.Node) NodeHandle {\n\tsourceFile := ast.GetSourceFileOfNode(node)\n\tpath := sourceFile.Path()\n\ttable := encoder.GetNodeIndexTable(sourceFile)\n\tidx := table.GetIndex(node)\n\treturn NodeHandle(fmt.Sprintf(\"%d.%d.%s\", idx, node.Kind, path))\n}\n\n// getOrCreateProjectRegistry returns the registry for the given project, creating it if needed.\nfunc (sd *snapshotData) getOrCreateProjectRegistry(projectID ProjectID) *projectRegistryData {\n\tif projectID == \"\" {\n\t\tpanic(\"getOrCreateProjectRegistry: empty project ID\")","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/session.go#L79-L115","documentation":"The snapshot's ProjectCollection has no project at the path given by the ProjectID handle (a ProjectID is the project's path string; parseProjectHandle extracts it and GetProjectByPath returns nil). Every project-scoped request fails with \"api: client error: project <path> not found\". Project membership is per-snapshot: a project handle is only valid inside the snapshot whose updateSnapshot produced it.","triggerScenarios":"Passing a ProjectID obtained from a previous snapshot after updateSnapshot rebuilt the project set; passing a hand-written tsconfig path instead of a server-returned handle; querying a project whose files were never opened in this session (projects materialize from open files); mixing handles across two concurrent sessions/processes.","commonSituations":"Client caches project handles across snapshot updates; editor tooling opening a new file set that drops a project; splitting a monorepo query across sessions; assuming ProjectID equals the tsconfig.json path the client computed itself.","solutions":["Use only handles the server returned for the current snapshot: getDefaultProjectForFile, initialize/updateSnapshot responses, or the Project field on SymbolResponse","If the project is missing, open its files via updateSnapshot so the project materializes, then re-fetch the handle","Scope cached handles: key them by (session, snapshot) and invalidate on every updateSnapshot","Never synthesize a ProjectID client-side from a filesystem path"],"exampleFix":"// before\nconst cached = localStorage.get(\"projectHandle\"); // handle from an older snapshot\nawait call(\"getTypeOfSymbol\", { snapshot, project: cached, symbol }); // project not found\n\n// after\nconst { project } = await call(\"getDefaultProjectForFile\", { snapshot, file });\nawait call(\"getTypeOfSymbol\", { snapshot, project, symbol });","handlingStrategy":"validation","validationCode":"// TS client: keep the set of live (snapshot -> projects) and validate before sending.\nfunction assertKnownProject(snapshot: bigint, project: string) {\n  const known = liveProjects.get(snapshot);\n  if (!known || !known.has(project)) {\n    throw new Error(`project ${project} not known in snapshot ${snapshot}; refresh via getDefaultProjectForFile/updateSnapshot`);\n  }\n}\n// record: liveProjects.get(resp.snapshot)?.add(resp.project) on every project-bearing response","typeGuard":"const isLiveProjectHandle = (p: string | undefined | null, snapshot: bigint, known: Set<string>): p is string =>\n  typeof p === \"string\" && p.length > 0 && known.has(`${snapshot}:${p}`);","tryCatchPattern":"try {\n  return await call(method, params);\n} catch (e) {\n  if (String(e).includes(\"project \") && String(e).includes(\" not found\")) {\n    const { project } = await call(\"getDefaultProjectForFile\", { snapshot: params.snapshot, file });\n    return await call(method, { ...params, project }); // retry once with a server-issued handle\n  }\n  throw e;\n}","preventionTips":["Only use ProjectIDs returned by the server (getDefaultProjectForFile, symbol/type responses)","Key cached project handles by snapshot id and clear them on every updateSnapshot","Open a project's files before expecting its handle to resolve","Never construct a ProjectID from a client-side tsconfig path"],"tags":["project","handle","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"}