microsoft/typescript-go · error
CFArrayCreate returned NULL
Error message
CFArrayCreate returned NULL
What it means
On macOS, after the per-path CFStrings are built, FSEvents setup gathers them into a CFArray; if CFArrayCreate returns NULL (again an allocation failure at the CoreFoundation layer), stream creation aborts with this sentinel. The library batches at most 512 paths per stream (fseventsPathsPerStream), so hitting this means memory exhaustion rather than a path-count limit.
Source
Thrown at internal/fswatch/fsevents_darwin.go:214
b.notifyStarted()
return nil
}
// checkWatcher mirrors the helper of the same name.
func checkWatcher(w *dirWatch) error {
info, err := os.Stat(w.physicalDir)
if err != nil {
return &dirWatchError{err: err, dirWatch: w}
}
if !info.IsDir() {
return &dirWatchError{err: syscall.ENOTDIR, dirWatch: w}
}
return nil
}
var (
errCFStringCreateNull = errors.New("CFStringCreate returned NULL")
errCFArrayCreateNull = errors.New("CFArrayCreate returned NULL")
errStreamCreateNull = errors.New("FSEventStreamCreate returned NULL")
errStreamStartFailed = errors.New("error starting FSEvents stream")
)
var (
errFSEventsUserDropped = fmt.Errorf("events were dropped by the FSEvents client: %w", ErrOverflow)
errFSEventsKernelDropped = fmt.Errorf("events were dropped by the kernel: %w", ErrOverflow)
errFSEventsTooMany = fmt.Errorf("too many events: %w", ErrOverflow)
)
const fseventsPathsPerStream = 512
type fseventsWatchSnapshot struct {
w *dirWatch
state *fseventsState
}
func (b *fsEventsBackend) activeWatchesLocked() []fseventsWatchSnapshot {View on GitHub (pinned to 1bcfa18d79)
Solutions
- Restart or trim the process to release memory, then recreate the watcher
- Lower the number of simultaneously watched directories; rely on WithRecursive rather than enumerating subtrees
- Retry stream creation with backoff after freeing resources
Example fix
// before
ws, err := fswatch.Default().WatchDirectories(requests) // CFArrayCreate NULL under pressure
// after: fewer, broader watches + retry
w, err := fswatch.Default().WatchDirectory(root, cb, fswatch.WithRecursive())
if err != nil && isCFAllocFailure(err) {
time.Sleep(2 * time.Second) // let memory settle
w, err = fswatch.Default().WatchDirectory(root, cb, fswatch.WithRecursive())
} Defensive patterns
Strategy: retry
Type guard
func isCFAllocFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "CFArrayCreate returned NULL")
} Try / catch
ws, err := watcher.WatchDirectories(reqs)
if err != nil && isCFAllocFailure(err) {
runtime.GC() // release Go-side garbage holding CF memory
time.Sleep(time.Second)
ws, err = watcher.WatchDirectories(reqs)
if err != nil && isCFAllocFailure(err) {
return startPolling(roots(reqs))
}
}
if err != nil { return err } Prevention
- Keep the path count per WatchDirectories batch modest; let WithRecursive cover the tree
- Shut down and recreate watchers after long uptime instead of accumulating subscriptions
- Investigate leaks with Instruments/heap profiles when this error appears in steady state
When it happens
Trigger: Watching enough paths that even the batched CFArray allocation fails under memory pressure; heap fragmentation in a long-lived process; system-wide memory pressure on macOS causing CF allocations to fail.
Common situations: Recursive watchers materializing many directory entries; daemons that leaked memory over days; CI jobs running many watchers in parallel on one runner.
Related errors
- CFStringCreate returned NULL
- FSEventStreamCreate returned NULL
- unable to open pipe: %w
- unable to initialize fanotify: %w
- unable to poll: %w
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/ad499ddaa8a84a7b.
Report an issue: GitHub.