{"record":{"id":"c6157bea1339ef3b","repo":"dmtrKovalenko/fff","slug":"watch-callback-must-be-a-function-c6157b","errorCode":null,"errorMessage":"watch callback must be a function","messagePattern":"watch callback must be a function","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/fff-node/src/finder.ts","lineNumber":658,"sourceCode":"    options?: WatchOptions,\n  ): Result<WatchUnsubscribe>;\n  watch(\n    patternOrCallback: string | WatchBatchCallback,\n    callbackOrOptions?: WatchBatchCallback | WatchOptions,\n    maybeOptions?: WatchOptions,\n  ): Result<WatchUnsubscribe> {\n    // Overload shift: watch(cb, opts?) -> empty pattern = whole tree.\n    const noPattern = typeof patternOrCallback === \"function\";\n    const pattern = noPattern ? \"\" : patternOrCallback;\n    const callback = noPattern\n      ? patternOrCallback\n      : (callbackOrOptions as WatchBatchCallback);\n    const options = noPattern\n      ? (callbackOrOptions as WatchOptions | undefined)\n      : maybeOptions;\n\n    if (typeof callback !== \"function\") {\n      return err(\"watch callback must be a function\");\n    }\n\n    const guard = this.ensureAlive();\n    if (!guard.ok) return guard;\n\n    const created = ffiWatch(guard.value, pattern, options?.ignore ?? [], callback);\n    if (!created.ok) return created;\n\n    const watchId = created.value;\n    this.watchers.add(watchId);\n\n    return {\n      ok: true,\n      value: () => {\n        if (!this.watchers.delete(watchId)) return;\n        if (this.handle !== null) ffiUnwatch(this.handle, watchId);\n      },\n    };","sourceCodeStart":640,"sourceCodeEnd":676,"githubUrl":"https://github.com/dmtrKovalenko/fff/blob/7f8537e70f0ea1210f9acbbfc4640141105cdc78/packages/fff-node/src/finder.ts#L640-L676","documentation":"The watch() method accepts a callback (with optional pattern and options), and the resolved callback argument must be a function. If the first/last argument resolved as the callback is not callable, watch() rejects the call before touching the native layer, since ffiWatch must register a JS callback to be invoked on file events.","triggerScenarios":"Calling finder.watch() with no arguments; passing an options object as the only argument without a callback (and no trailing callback); passing a non-function such as a string, an async wrapper misused as a value, or a callback stored under a different property name.","commonSituations":"Misreading the overload signature and passing (options) only; refactoring from an event-emitter style API and forgetting the callback argument; passing a callback property like { callback: fn } instead of fn itself.","solutions":["Pass a function as the callback argument: finder.watch(pattern, options, cb) or finder.watch(cb).","Check typeof callback === 'function' before calling watch when the callback is dynamic.","If you only have an options object, add the callback explicitly instead of relying on the overload resolution.","Review the watch() signature in finder.ts to confirm argument order for the overload you intend."],"exampleFix":"// before\nfinder.watch({ ignore: ['node_modules'] }); // no callback\n// after\nfinder.watch({ ignore: ['node_modules'] }, (events) => {\n  console.log('watch events', events);\n});","handlingStrategy":"validation","validationCode":"if (typeof cb !== 'function') throw new TypeError('watch requires a callback function');","typeGuard":"function isWatchCallback(v: unknown): v is WatchBatchCallback {\n  return typeof v === 'function';\n}","tryCatchPattern":"const res = finder.watch(pattern, opts, cb);\nif (!res.ok && res.error.includes('callback must be a function')) {\n  throw new TypeError('Pass a function to watch(), got: ' + typeof cb);\n}","preventionTips":["Always pass the callback last: watch(pattern?, options?, callback).","Check typeof before calling when the callback is dynamic.","Do not nest the callback inside the options object.","Re-read the watch() overload docs after API upgrades."],"tags":["validation","watch","callback"],"backgroundTag":"missing-required-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"}