microsoft/typescript-go · error

project not found for update: %s

Error message

project not found for update: %s

What it means

Raised during the update phase of project collection building: the builder iterates apiState.openProjects (accumulated from earlier open requests) and must run updateProgram on each — but a config path is present in open-projects state while b.configuredProjects no longer has an entry for it. That means the project was closed/evicted from the registry while the API still counts it as open, an internal state divergence the builder refuses to paper over.

Source

Thrown at internal/project/projectcollectionbuilder.go:212

	if apiRequest.OpenFiles != nil {
		for uri := range apiRequest.OpenFiles.Keys() {
			fileName := uri.FileName()
			path := b.toPath(fileName)
			if b.apiState.openFiles == nil {
				b.apiState.openFiles = make(map[tspath.Path]apiOpenedFile)
			}
			entry := b.apiState.openFiles[path]
			entry.fileName = fileName
			entry.refCount++
			b.apiState.openFiles[path] = entry
		}
	}

	for configPath := range b.apiState.openProjects {
		if entry, ok := b.configuredProjects.Load(configPath); ok {
			b.updateProgram(entry, logger)
		} else {
			return fmt.Errorf("project not found for update: %s", configPath)
		}
	}

	for _, overlay := range b.fs.overlays {
		if entry := b.findDefaultConfiguredProject(overlay.FileName(), b.toPath(overlay.FileName())); entry != nil {
			delete(projectsToClose, entry.Value().configFilePath)
		}
	}

	for projectPath := range projectsToClose {
		if entry, ok := b.configuredProjects.Load(projectPath); ok {
			b.deleteConfiguredProject(entry, logger)
		}
	}

	// Ensure each API-opened file is placed like LSP's textDocument/didOpen: search
	// up ancestor directories for a configured project that contains it, and only
	// fall back to the inferred project if none is found. This also keeps already

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Re-open the project (it will be re-created via findOrCreateProject) before issuing the next update
  2. Serialize open/close/update requests per config path instead of racing them
  3. If the project was intentionally closed, also remove it from your open-projects tracking so updates skip it

Example fix

// before
api.Update(ctx, req) // openProjects still lists a closed project

// after
if !api.IsProjectOpen(configPath) {
	api.Open(ctx, configPath) // re-materialize before update
}
api.Update(ctx, req)
Defensive patterns

Strategy: validation

Validate before calling

// verify the project is still open before issuing updates
for cfg := range myOpenProjects {
	if !apiSession.ProjectExists(cfg) {
		if err := apiSession.Open(cfg); err != nil {
			return err
		}
	}
}
apiSession.Update(ctx)

Try / catch

err := builder.Build(apiRequest)
if err != nil && strings.Contains(err.Error(), "project not found for update") {
	cfgPath := extractPath(err) // parse %s from the message
	apiSession.Open(cfgPath)   // re-materialize, then retry once
	return builder.Build(apiRequest)
}

Prevention

When it happens

Trigger: Project closed (config file deleted, or a close-projects request) between the open call and the next update; failed loads evicting the configured project while refcounts in apiState remain; interleaved open/close/update API requests hitting a race; config file invalidated by watch events mid-sequence.

Common situations: Rapid open→update sequences from automation while file watchers concurrently remove projects; tsconfig deleted between requests; concurrent API clients one closing and one updating the same project.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/b015aef802e49fb1. Report an issue: GitHub.