microsoft/typescript-go · warning

failed to unregister file watcher: %w

Error message

failed to unregister file watcher: %w

What it means

UnwatchFiles (server.go:281-283) with the builtin watcher active: the id was registered, but the underlying lspwatcher backend failed to remove the OS-level watch; the error wraps the backend failure with %w. Set deletion is skipped on this path, so the id remains registered — the caller can retry.

Source

Thrown at internal/lsp/server.go:282

			},
		},
	})
	if err != nil {
		return fmt.Errorf("failed to register file watcher: %w", err)
	}

	s.watchers.Add(id)
	return nil
}

// UnwatchFiles implements project.Client.
func (s *Server) UnwatchFiles(ctx context.Context, id project.WatcherID) error {
	if s.builtinWatcher != nil {
		if !s.watchers.Has(id) {
			return fmt.Errorf("no file watcher exists with ID %s", id)
		}
		if err := s.builtinWatcher.UnwatchFiles(string(id)); err != nil {
			return fmt.Errorf("failed to unregister file watcher: %w", err)
		}
		s.watchers.Delete(id)
		return nil
	}
	if s.watchers.Has(id) {
		_, err := sendClientRequest(ctx, s, lsproto.ClientUnregisterCapabilityInfo, &lsproto.UnregistrationParams{
			Unregisterations: []*lsproto.Unregistration{
				{
					Id:     string(id),
					Method: string(lsproto.MethodWorkspaceDidChangeWatchedFiles),
				},
			},
		})
		if err != nil {
			return fmt.Errorf("failed to unregister file watcher: %w", err)
		}

		s.watchers.Delete(id)

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Retry UnwatchFiles once — because s.watchers still contains the id on this failure path, a retry is safe and often succeeds after transient backend errors.
  2. If the watched tree was deleted, the error is usually benign during teardown; log and continue.
  3. Check the wrapped backend error for actionable causes (handle invalid, path gone) before retrying.

Example fix

// before
if err := client.UnwatchFiles(ctx, id); err != nil { return err }
// after: tolerate failures for vanished paths during teardown
if err := client.UnwatchFiles(ctx, id); err != nil && !errors.Is(err, fs.ErrNotExist) {
    log.Printf("unwatch %s: %v", id, err)
}
Defensive patterns

Strategy: retry

Try / catch

if err := client.UnwatchFiles(ctx, id); err != nil {
    if strings.Contains(err.Error(), "failed to unregister file watcher") {
        // id remains registered on this path; one retry is safe
        if err2 := client.UnwatchFiles(ctx, id); err2 != nil {
            log.Printf("unwatch %s failed twice: %v / %v", id, err, err2)
        }
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: The OS watch handle is already gone (directory deleted), backend close errors, or a backend-specific failure while unwatching the pattern set registered under that id string.

Common situations: Projects whose watched directories are deleted or unmounted before cleanup; shutdown races where the watcher is closing concurrently; resource exhaustion making teardown fail.

Related errors


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