dmtrKovalenko/fff · error

Unsupported FffWatchOptions version

Error message

Unsupported FffWatchOptions version {} (library understands up to {})

What it means

watch_options_from_ffi validates the version field of an FffWatchOptions struct passed to fff_watch. Version 0 or a version greater than FFF_WATCH_OPTIONS_VERSION is rejected because the library cannot safely interpret the layout of an unknown options struct. The error is returned as an owned FffResult pointer to the caller.

Solutions

  1. Rebuild the Rust shared library (make build) so the .so matches the Lua/bindings version.
  2. Set opts.version to FFF_WATCH_OPTIONS_VERSION exactly as defined in the installed library's header.
  3. Ensure the FffWatchOptions struct is fully initialized, not zeroed with version left at 0.
  4. If the plugin updated its options struct, update the C FFI bindings/header to match.
  5. Avoid mixing fff-nvim versions: delete stale build artifacts before reinstalling.

Example fix

// before
let opts = FffWatchOptions { version: 0, ..Default::default() };
fff_watch(inst, &opts); // unsupported version 0
// after
let opts = FffWatchOptions { version: FFF_WATCH_OPTIONS_VERSION, ..Default::default() };
fff_watch(inst, &opts);
Defensive patterns

Strategy: validation

Validate before calling

assert(opts.version == C.FFF_WATCH_OPTIONS_VERSION,
  ('watch options version %d unsupported, need %d'):format(opts.version or 0, C.FFF_WATCH_OPTIONS_VERSION))

Type guard

local function valid_watch_opts(o)
  return type(o) == 'table' and o.version == C.FFF_WATCH_OPTIONS_VERSION
end

Try / catch

local res = fff.watch(inst, opts)
if type(res) == 'userdata' and is_err_result(res) then
  error('rebuild native lib: ' .. ffi.string(res.message))
end

Prevention

When it happens

Trigger: Calling fff_watch with FffWatchOptions.version set to 0 (uninitialized struct), a version from a newer fff-nvim release than the installed shared library, or garbage memory where the version field lands on 0/out-of-range.

Common situations: Lua side rebuilt/updated but the compiled Rust .so is stale (or vice versa) after upgrading the plugin; forgetting to set version = FFF_WATCH_OPTIONS_VERSION; a bindings codegen mismatch where struct layout changed; passing a zeroed struct without filling in version.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of dmtrKovalenko/fff@7f8537e70f (2026-09-10). Data as JSON: /api/errors/36a18ffd93cfd630. Report an issue: GitHub.

Appendix: source

Thrown at crates/fff-c/src/watch.rs:83

        std::mem::forget(boxed);
        p
    };

    Box::into_raw(Box::new(FffWatchEventBatch {
        events: events_ptr,
        count,
    }))
}

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")),
            }
        }

View on GitHub (pinned to 7f8537e70f)