{"record":{"id":"af34ad1777c0870f","repo":"remix-run/remix","slug":"hmr-must-create-a-channel-with-an-updatewatchedfil","errorCode":null,"errorMessage":"hmr must create a channel with an updateWatchedFiles function","messagePattern":"hmr must create a channel with an updateWatchedFiles function","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/assets/src/lib/asset-server.ts","lineNumber":1133,"sourceCode":"    return channel\n  })\n}\n\nfunction validateBrowserHmrChannel(channel: unknown): asserts channel is BrowserHmrChannel {\n  if (channel === null || typeof channel !== 'object') {\n    throw new TypeError('hmr must create an object')\n  }\n  if (!('url' in channel) || typeof channel.url !== 'string') {\n    throw new TypeError('hmr must create a channel with a string url')\n  }\n  if (!('close' in channel) || typeof channel.close !== 'function') {\n    throw new TypeError('hmr must create a channel with a close function')\n  }\n  if (!('onFileEvents' in channel) || typeof channel.onFileEvents !== 'function') {\n    throw new TypeError('hmr must create a channel with an onFileEvents function')\n  }\n  if (!('updateWatchedFiles' in channel) || typeof channel.updateWatchedFiles !== 'function') {\n    throw new TypeError('hmr must create a channel with an updateWatchedFiles function')\n  }\n}\n\nfunction normalizeBasePath(basePath: string): string {\n  if (typeof basePath !== 'string') {\n    throw new TypeError('basePath must be a string')\n  }\n\n  return normalizePathname(basePath || '/').replace(/\\/+$/, '') || '/'\n}\n\nfunction normalizeFingerprintOptions(options: {\n  fingerprint: AssetServerOptions['fingerprint']\n  watch: AssetServerOptions['watch']\n}):\n  | {\n      enabled: false\n      buildId?: string","sourceCodeStart":1115,"sourceCodeEnd":1151,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/assets/src/lib/asset-server.ts#L1115-L1151","documentation":"The HMR channel contract requires `updateWatchedFiles(files)` so the asset server can tell the channel which files it depends on (e.g. lazy compilation dependencies). This error fires when the object returned by `hmr.createChannel` lacks a callable `updateWatchedFiles`.","triggerScenarios":"`createChannel` returns a channel missing the `updateWatchedFiles` function, so the server cannot sync its watched-file set with the channel.","commonSituations":"Custom channels built against an earlier channel interface; minimal mock channels in plugins or tests that omit the watch-sync API.","solutions":["Add `updateWatchedFiles(files: string[]): void` to the channel that forwards the list to the underlying watcher (add/remove watches as needed)","Ensure `url`, `close`, and `onFileEvents` are also present","Use the default channel by omitting `hmr.createChannel`"],"exampleFix":"// before\ncreateChannel: () => ({ url, close, onFileEvents })\n// after\ncreateChannel: () => ({\n  url,\n  close,\n  onFileEvents,\n  updateWatchedFiles(files) {\n    watcher.add(files)\n  },\n})","handlingStrategy":"validation","validationCode":"let channel = createChannel()\nif (typeof channel.updateWatchedFiles !== 'function') throw new Error('channel must implement updateWatchedFiles')","typeGuard":"function hasUpdateWatchedFiles(c: unknown): c is { updateWatchedFiles: (files: string[]) => void } {\n  return !!c && typeof (c as any).updateWatchedFiles === 'function'\n}","tryCatchPattern":null,"preventionTips":["Re-use the same channel factory across dev setups so updateWatchedFiles is never forgotten","Assert the full interface in a smoke test before starting the server"],"tags":["hmr","dev-server","file-watching","validation"],"backgroundTag":"interface-contract-validation","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}