microsoft/typescript-go · error · dirWatchError
invalid handle: %w
Error message
invalid handle: %w
What it means
Synchronous error from WatchDirectory on Windows: CreateFile failed to open the watched directory with FILE_LIST_DIRECTORY access and FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED. The %w wraps the Win32 error (a windows.Errno), so errors.Is works against specific codes. Note the separate returns next to it: a successful open whose GetFileInformationByHandle fails yields errGetFileInfo, and a non-directory yields syscall.ENOTDIR.
Source
Thrown at internal/fswatch/windows.go:178
event windows.Handle
}
func newWindowsSubscription(watcherImpl *windowsBackend, w *dirWatch) (*windowsSubscription, error) {
pathPtr, err := windows.UTF16PtrFromString(w.physicalDir)
if err != nil {
return nil, &dirWatchError{err: err, dirWatch: w}
}
h, err := windows.CreateFile(
pathPtr,
windows.FILE_LIST_DIRECTORY,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
nil,
windows.OPEN_EXISTING,
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED,
0,
)
if err != nil {
return nil, &dirWatchError{err: fmt.Errorf("invalid handle: %w", err), dirWatch: w}
}
var info windows.ByHandleFileInformation
if err := windows.GetFileInformationByHandle(h, &info); err != nil {
_ = windows.CloseHandle(h)
return nil, &dirWatchError{err: errGetFileInfo, dirWatch: w}
}
if info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 {
_ = windows.CloseHandle(h)
return nil, &dirWatchError{err: syscall.ENOTDIR, dirWatch: w}
}
return &windowsSubscription{
watcherImpl: watcherImpl,
dirWatch: w,
handle: h,
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
bufBytes: defaultBufSize,View on GitHub (pinned to 1bcfa18d79)
Solutions
- Verify with os.Stat(dir) that the path exists and is a directory immediately before subscribing
- Fix ACLs or run under an account with list-directory rights on the target
- Retry in a loop when the directory is created asynchronously at startup
- Branch on the wrapped errno: errors.Is(err, windows.ERROR_FILE_NOT_FOUND) suggests a race, ERROR_ACCESS_DENIED suggests permissions
Example fix
// before
watch, err := fswatch.Windows().WatchDirectory(dir, cb)
if err != nil {
return err // no retry: races with dir creation fail permanently
}
// after
var watch fswatch.Watch
for attempt := 0; attempt < 10; attempt++ {
var err error
watch, err = fswatch.Windows().WatchDirectory(dir, cb)
if err == nil {
break
}
if !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) && !errors.Is(err, windows.ERROR_PATH_NOT_FOUND) {
return err
}
time.Sleep(100 * time.Millisecond)
} Defensive patterns
Strategy: try-catch
Validate before calling
info, err := os.Stat(dir)
if err != nil {
return err
}
if !info.IsDir() {
return syscall.ENOTDIR
} Try / catch
watch, err := fswatch.Windows().WatchDirectory(dir, cb)
if err != nil {
switch {
case errors.Is(err, windows.ERROR_FILE_NOT_FOUND), errors.Is(err, windows.ERROR_PATH_NOT_FOUND):
// race with deletion/creation: retry
case errors.Is(err, windows.ERROR_ACCESS_DENIED):
// fix ACLs or account
default:
return err
}
} Prevention
- Stat the directory immediately before subscribing
- Run the watcher under an account with list-directory rights
- Retry around startup races when directories are created asynchronously
- Keep watch roots off paths that build steps delete and recreate
When it happens
Trigger: Directory does not exist (ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND), for example deleted between your existence check and the subscribe call. Access denied (ERROR_ACCESS_DENIED) because the process lacks list-directory rights. Sharing or path problems on unusual reparse points.
Common situations: Watching a directory a build step just removed. Unprivileged service accounts watching protected paths. Misconfigured paths, including typos and wrong case on case-sensitive volumes.
Related errors
- error watching %s: %w
- error opening directory: %w
- could not get file information
- Language client is not initialized
- Child process exited with code ${this.child.exitCode} before
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/96a480dd9c7c0ba6.
Report an issue: GitHub.