remix-run/remix · error · TypeError

hmr must create a channel with an updateWatchedFiles functio

Error message

hmr must create a channel with an updateWatchedFiles function

What it means

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`.

Source

Thrown at packages/assets/src/lib/asset-server.ts:1133

    return channel
  })
}

function validateBrowserHmrChannel(channel: unknown): asserts channel is BrowserHmrChannel {
  if (channel === null || typeof channel !== 'object') {
    throw new TypeError('hmr must create an object')
  }
  if (!('url' in channel) || typeof channel.url !== 'string') {
    throw new TypeError('hmr must create a channel with a string url')
  }
  if (!('close' in channel) || typeof channel.close !== 'function') {
    throw new TypeError('hmr must create a channel with a close function')
  }
  if (!('onFileEvents' in channel) || typeof channel.onFileEvents !== 'function') {
    throw new TypeError('hmr must create a channel with an onFileEvents function')
  }
  if (!('updateWatchedFiles' in channel) || typeof channel.updateWatchedFiles !== 'function') {
    throw new TypeError('hmr must create a channel with an updateWatchedFiles function')
  }
}

function normalizeBasePath(basePath: string): string {
  if (typeof basePath !== 'string') {
    throw new TypeError('basePath must be a string')
  }

  return normalizePathname(basePath || '/').replace(/\/+$/, '') || '/'
}

function normalizeFingerprintOptions(options: {
  fingerprint: AssetServerOptions['fingerprint']
  watch: AssetServerOptions['watch']
}):
  | {
      enabled: false
      buildId?: string

View on GitHub (pinned to 9696913134)

Solutions

  1. Add `updateWatchedFiles(files: string[]): void` to the channel that forwards the list to the underlying watcher (add/remove watches as needed)
  2. Ensure `url`, `close`, and `onFileEvents` are also present
  3. Use the default channel by omitting `hmr.createChannel`

Example fix

// before
createChannel: () => ({ url, close, onFileEvents })
// after
createChannel: () => ({
  url,
  close,
  onFileEvents,
  updateWatchedFiles(files) {
    watcher.add(files)
  },
})
Defensive patterns

Strategy: validation

Validate before calling

let channel = createChannel()
if (typeof channel.updateWatchedFiles !== 'function') throw new Error('channel must implement updateWatchedFiles')

Type guard

function hasUpdateWatchedFiles(c: unknown): c is { updateWatchedFiles: (files: string[]) => void } {
  return !!c && typeof (c as any).updateWatchedFiles === 'function'
}

Prevention

When it happens

Trigger: `createChannel` returns a channel missing the `updateWatchedFiles` function, so the server cannot sync its watched-file set with the channel.

Common situations: Custom channels built against an earlier channel interface; minimal mock channels in plugins or tests that omit the watch-sync API.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/af34ad1777c0870f. Report an issue: GitHub.