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

  1. Restart or trim the process to release memory, then recreate the watcher
  2. Lower the number of simultaneously watched directories; rely on WithRecursive rather than enumerating subtrees
  3. 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

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


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