{"record":{"id":"1f6fe10beb10fc20","repo":"dmtrKovalenko/fff","slug":"scan-progress-returned-null","errorCode":null,"errorMessage":"scan progress returned null","messagePattern":"scan progress returned null","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/fff-bun/src/ffi.ts","lineNumber":1352,"sourceCode":"}\n\n// FffScanProgress { scanned_files_count: u64(8), is_scanning: bool(1), is_watcher_ready: bool(1), is_warmup_complete: bool(1) + pad }\nconst SP_COUNT = 0; // u64 (8)\nconst SP_SCANNING = 8; // bool (1)\nconst SP_WATCHER_READY = 9; // bool (1)\nconst SP_WARMUP_COMPLETE = 10; // bool (1)\n\n/**\n * Get scan progress.\n */\nexport function ffiGetScanProgress(handle: NativeHandle): Result<ScanProgress> {\n  const library = loadLibrary();\n  const resultPtr = library.symbols.fff_get_scan_progress(handle);\n  const envelope = readResultEnvelope(resultPtr);\n  if (!(\"success\" in envelope)) return envelope;\n\n  if (envelope.handlePtr === 0) {\n    return err(\"scan progress returned null\");\n  }\n\n  const hp = asPtr(envelope.handlePtr);\n  const result: ScanProgress = {\n    scannedFilesCount: Number(read.u64(hp, SP_COUNT)),\n    isScanning: read.u8(hp, SP_SCANNING) !== 0,\n    isWatcherReady: read.u8(hp, SP_WATCHER_READY) !== 0,\n    isWarmupComplete: read.u8(hp, SP_WARMUP_COMPLETE) !== 0,\n  };\n  library.symbols.fff_free_scan_progress(hp);\n  return { ok: true, value: result };\n}\n\n/**\n * Wait for scan to complete.\n */\nexport function ffiWaitForScan(handle: NativeHandle, timeoutMs: number): Result<boolean> {\n  const library = loadLibrary();","sourceCodeStart":1334,"sourceCodeEnd":1370,"githubUrl":"https://github.com/dmtrKovalenko/fff/blob/7f8537e70f0ea1210f9acbbfc4640141105cdc78/packages/fff-bun/src/ffi.ts#L1334-L1370","documentation":"ffiGetScanProgress received a success envelope from fff_get_scan_progress whose handle pointer is 0, meaning the native side returned no ScanProgress struct. The binding maps this to an error instead of defaulting the progress fields.","triggerScenarios":"Calling ffiGetScanProgress on a handle whose native instance no longer exists (already destroyed/invalid), or a native failure to allocate the progress struct while still reporting success.","commonSituations":"Polling scan progress from a timer after the finder was closed; polling before init completed with a stale handle; version-mismatched native binary.","solutions":["Stop polling scan progress once the instance is destroyed; guard the call with an aliveness check.","Only call fff_get_scan_progress with the handle returned by instance creation, kept valid until teardown.","Rebuild the native library if the error occurs on a live instance."],"exampleFix":"// before\nsetInterval(() => ffiGetScanProgress(handle), 500); // keeps polling after destroy()\n// after\nsetInterval(() => { if (handle !== null) ffiGetScanProgress(handle); }, 500);","handlingStrategy":"type-guard","validationCode":"if (finderHandle === null) return { scannedFilesCount: 0, isScanning: false };","typeGuard":"function canPollProgress(finder) { return finder != null && finder.handle !== null; }","tryCatchPattern":"try { const p = await finder.getScanProgress(); } catch (e) { if (e.message.includes('scan progress returned null')) stopProgressPolling(); }","preventionTips":["Stop progress timers in the destroy() path.","Only poll progress with the live handle from instance creation.","Treat this error as 'instance gone' and tear down the polling loop."],"tags":["ffi","null-pointer","bun","scan-progress"],"backgroundTag":"null-argument","analyzedSha":"7f8537e70f0ea1210f9acbbfc4640141105cdc78","analyzedAt":"2026-09-10T07:26:53.407Z","contentChangedAt":"2026-09-10T07:26:53.407Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}