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
- In the callback, branch on errors.Is(err, fswatch.ErrWatchTerminated) and call Close() on the Watch
- Drill into the wrapped cause (errors.Unwrap, errors.As on syscall.Errno) and log it to identify the real failure
- Fix the environmental cause (limits, share availability, permissions) before re-subscribing
- 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
- Always unwrap the cause under ErrWatchTerminated before choosing a fix
- Close dead watches so subscriptions and handles are released
- Fix environmental causes (limits, shares, permissions) before re-subscribing
- Log backend causes: recurring patterns point to system-level issues
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
- fswatch: watch terminated
- %w: watched directory removed
- failed to read changes
- fswatch: path must be absolute
- fswatch: event overflow; some changes were missed
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/0b5f76262442631b.
Report an issue: GitHub.