dmtrKovalenko/fff · error
ignore_count > 0 but ignore is NULL
Error message
ignore_count > 0 but ignore is NULL
What it means
fff_watch's FFI options struct carries an ignore list as (pointer, count) pair. If ignore_count is non-zero but the ignore pointer is NULL, the C API cannot read any entries, so watch_options_from_ffi rejects the options. This guards against a mismatched or zero-initialized WatchOptions struct produced by an FFI caller.
Solutions
- Set opts.ignore to the pointer of the allocated char** array whenever ignore_count > 0.
- If you have no ignore patterns, set both ignore_count = 0 and ignore = NULL.
- Keep the ignore array alive until fff_watch returns (pin it / hold the reference) so the pointer is not nulled by GC.
Example fix
// before
let opts = FfiWatchOptions { ignore_count: patterns.len() as u32, ignore: null() };
// after
let opts = FfiWatchOptions { ignore_count: patterns.len() as u32, ignore: patternsPtr }; Defensive patterns
Strategy: validation
Validate before calling
function validateWatchOptions(opts) {
if (opts.ignore_count > 0 && (opts.ignore === null || opts.ignore === undefined)) {
throw new Error('ignore array required when ignore_count > 0');
}
} Type guard
function hasIgnoreArray(opts) { return opts.ignore_count === 0 || (opts.ignore !== null && opts.ignore !== undefined); } Try / catch
try { startWatch(opts); } catch (e) { if (String(e).includes('ignore is NULL')) console.error('FfiWatchOptions built incorrectly: pointer/count mismatch'); } Prevention
- Use a constructor/builder for FfiWatchOptions that keeps pointer and count in sync.
- Pass 0/NULL together when there is no ignore list.
- Pin the ignore array in memory until the watch call returns.
When it happens
Trigger: Calling fff_watch (directly or via fff_watch_args / bun ffiSetWatch) with an FfiWatchOptions where ignore_count > 0 but ignore is NULL — e.g. struct zero-initialized, count set manually, pointer field forgotten, or GC collected the array before the call.
Common situations: Developers hand-building the FFI struct in C/Rust FFI bindings set `count` from an array length but forget to assign the array pointer; or use a zeroed struct with `#[repr(C)]` and only fill ignore_count.
Related errors
- Query is null or invalid UTF-8
- File path is null or invalid UTF-8
- Instance handle is null. Create one with…
- opts is null
- File picker not initialized. Call fff_create_instance first.
AI-assisted analysis of dmtrKovalenko/fff@7f8537e70f (2026-09-10).
Data as JSON: /api/errors/90dc1f1b15fa5b76.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fff-c/src/watch.rs:92
unsafe fn watch_options_from_ffi(
opts: *const FffWatchOptions,
) -> Result<WatchOptions, *mut FffResult> {
if opts.is_null() {
return Ok(WatchOptions::default());
}
let opts = unsafe { &*opts };
if opts.version == 0 || opts.version > FFF_WATCH_OPTIONS_VERSION {
return Err(FffResult::err(&format!(
"Unsupported FffWatchOptions version {} (library understands up to {})",
opts.version, FFF_WATCH_OPTIONS_VERSION
)));
}
let mut ignore = Vec::with_capacity(opts.ignore_count as usize);
if opts.ignore_count > 0 {
if opts.ignore.is_null() {
return Err(FffResult::err("ignore_count > 0 but ignore is NULL"));
}
for i in 0..opts.ignore_count as usize {
let entry = unsafe { *opts.ignore.add(i) };
match unsafe { crate::cstr_to_str(entry) } {
Some(s) if !s.is_empty() => ignore.push(s.to_string()),
Some(_) => {}
None => return Err(FffResult::err("ignore entry is NULL or invalid UTF-8")),
}
}
}
Ok(WatchOptions { ignore })
}
// The caller guarantees user_data is safe on the callback thread.
struct UserData(*mut c_void);
unsafe impl Send for UserData {}
unsafe impl Sync for UserData {}View on GitHub (pinned to 7f8537e70f)