microsoft/typescript-go · error

%w: %w

Error message

%w: %w

What it means

The generic terminal wrap: watcherBase.handleWatcherError takes any backend *dirWatchError, removes the watch via watchRemove, and delivers fmt.Errorf("%w: %w", ErrWatchTerminated, werr) to the directory's callbacks. Because both layers unwrap, errors.Is matches ErrWatchTerminated as well as the underlying backend error. This is the delivery path for fatal errors once a watch is running (Windows fatal(), backend subscription failures surfaced asynchronously).

Source

Thrown at internal/fswatch/watcher.go:768

	}
	b.mu.Unlock()
	return nil
}

func (b *watcherBase) watchRemove(w *dirWatch) {
	b.mu.Lock()
	if _, ok := b.subscriptions[w]; !ok {
		b.mu.Unlock()
		return
	}
	delete(b.subscriptions, w)
	_ = b.self.closeWatch(w)
	b.mu.Unlock()
}

func (b *watcherBase) handleWatcherError(werr *dirWatchError) {
	b.watchRemove(werr.dirWatch)
	werr.dirWatch.notifyError(fmt.Errorf("%w: %w", ErrWatchTerminated, werr))
}

// ----- dirWatch: per-directory watch state -------------------------

type callback struct {
	id               uint64
	dir              string
	physicalDir      string
	watchDir         string
	watchPhysicalDir string
	recursive        bool
	fn               WatchCallback
	ignore           func(path string) bool
	sinceSeq         uint64
	terminal         error
	delivered        bool
}

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. In the callback, branch on errors.Is(err, fswatch.ErrWatchTerminated) and call Close() on the Watch
  2. Drill into the wrapped cause (errors.Unwrap, errors.As on syscall.Errno) and log it to identify the real failure
  3. Fix the environmental cause (limits, share availability, permissions) before re-subscribing
  4. Re-subscribe with WatchDirectory once the cause is resolved

Example fix

// before
func(events []fswatch.Event, err error) {
    if err != nil {
        log.Printf("watch error: %v", err) // no recovery, watch leaks
    }
}

// after
func(events []fswatch.Event, err error) {
    if errors.Is(err, fswatch.ErrWatchTerminated) {
        log.Printf("watch terminated: %v", errors.Unwrap(err))
        watch.Close()
        go resubscribeWhenReady(dir)
        return
    }
    apply(events)
}
Defensive patterns

Strategy: try-catch

Type guard

func isWatchTerminated(err error) bool {
    return errors.Is(err, fswatch.ErrWatchTerminated)
}

Try / catch

func(events []fswatch.Event, err error) {
    if errors.Is(err, fswatch.ErrWatchTerminated) {
        cause := errors.Unwrap(err)
        log.Printf("watch died: %v", cause)
        watch.Close()
        return
    }
    apply(events)
}

Prevention

When it happens

Trigger: A running watch hits an unrecoverable backend error: Windows beginRead, GetOverlappedResult, or unrecognized completion errors routed through fatal(); kqueue or other backend dirWatchErrors delivered after the watch started. The callback receives ErrWatchTerminated with the backend error as the wrapped cause.

Common situations: Network shares dropping or removable media ejected mid-watch. Resource exhaustion that only appears after the watch has been alive for a while. Handle invalidation by filter drivers.

Related errors


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