microsoft/typescript-go · error · ErrClientError
%w: project %s not found
Error message
%w: project %s not found
What it means
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.
Source
Thrown at internal/api/session.go:97
proj, err := sd.getProject(projectHandle)
if err != nil {
return nil, err
}
program := proj.GetProgram()
if program == nil {
return nil, fmt.Errorf("%w: project has no program", ErrClientError)
}
return program, nil
}
// getProject looks up a project from a project handle within this snapshot.
func (sd *snapshotData) getProject(projectHandle ProjectID) (*project.Project, error) {
projectName := parseProjectHandle(projectHandle)
proj := sd.snapshot.ProjectCollection.GetProjectByPath(projectName)
if proj == nil {
return nil, fmt.Errorf("%w: project %s not found", ErrClientError, projectName)
}
return proj, nil
}
// nodeHandleFrom creates an index-based node handle (index.kind.path), building a node index table
// for the file on-demand if needed.
func (sd *snapshotData) nodeHandleFrom(node *ast.Node) NodeHandle {
sourceFile := ast.GetSourceFileOfNode(node)
path := sourceFile.Path()
table := encoder.GetNodeIndexTable(sourceFile)
idx := table.GetIndex(node)
return NodeHandle(fmt.Sprintf("%d.%d.%s", idx, node.Kind, path))
}
// getOrCreateProjectRegistry returns the registry for the given project, creating it if needed.
func (sd *snapshotData) getOrCreateProjectRegistry(projectID ProjectID) *projectRegistryData {
if projectID == "" {
panic("getOrCreateProjectRegistry: empty project ID")View on GitHub (pinned to 1bcfa18d79)
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
Example fix
// before
const cached = localStorage.get("projectHandle"); // handle from an older snapshot
await call("getTypeOfSymbol", { snapshot, project: cached, symbol }); // project not found
// after
const { project } = await call("getDefaultProjectForFile", { snapshot, file });
await call("getTypeOfSymbol", { snapshot, project, symbol }); Defensive patterns
Strategy: validation
Validate before calling
// TS client: keep the set of live (snapshot -> projects) and validate before sending.
function assertKnownProject(snapshot: bigint, project: string) {
const known = liveProjects.get(snapshot);
if (!known || !known.has(project)) {
throw new Error(`project ${project} not known in snapshot ${snapshot}; refresh via getDefaultProjectForFile/updateSnapshot`);
}
}
// record: liveProjects.get(resp.snapshot)?.add(resp.project) on every project-bearing response Type guard
const isLiveProjectHandle = (p: string | undefined | null, snapshot: bigint, known: Set<string>): p is string =>
typeof p === "string" && p.length > 0 && known.has(`${snapshot}:${p}`); Try / catch
try {
return await call(method, params);
} catch (e) {
if (String(e).includes("project ") && String(e).includes(" not found")) {
const { project } = await call("getDefaultProjectForFile", { snapshot: params.snapshot, file });
return await call(method, { ...params, project }); // retry once with a server-issued handle
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- %w: type handle %d not found (no registry for project %s)
- %w: type handle %d not found in project registry
- %w: signature handle %d not found (no registry for project %
- %w: signature handle %d not found in project registry
- %w: snapshot %d not found
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/feacc2f37d9e82ca.
Report an issue: GitHub.