remix-run/remix · error · TypeError

hmr must be a function

Error message

hmr must be a function

What it means

Some `remix test` options require a positive integer. `optionalPositiveInteger` first runs the number check, then rejects values that aren't integers or are below 1 — zero, negatives, decimals, and Infinity all fail `Number.isInteger(n) && n >= 1`.

Source

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

      {
        mounts,
        rootDir,
      },
      ...getInjectedPackageMountConfigs(),
    ]),
    sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
    sourceMaps: options.sourceMaps,
    scriptsTarget: resolveScriptTarget(options.target),
    stylesTarget: resolveStyleTarget(options.target),
    watchOptions,
  }
}

function normalizeHmrFactory(factory: AssetServerOptions['hmr']): BrowserHmrChannelFactory | null {
  if (factory === undefined) return null

  if (typeof factory !== 'function') {
    throw new TypeError('hmr must be a function')
  }

  return factory
}

function createBrowserHmrChannel(
  createChannel: BrowserHmrChannelFactory | null,
  handleFileEvents: BrowserHmrFileEventHandler,
  isClosed: () => boolean,
  setUnsubscribe: (unsubscribe: () => void) => void,
): Promise<BrowserHmrChannel | null> {
  if (!createChannel) return Promise.resolve(null)

  let channelOrPromise: BrowserHmrChannel | undefined | Promise<BrowserHmrChannel | undefined>
  try {
    channelOrPromise = createChannel()
  } catch (error) {
    return Promise.reject(error)

View on GitHub (pinned to 9696913134)

Solutions

  1. Use a value of 1 or greater: `--retries 2`
  2. Default computed zero/empty values to omitting the flag entirely
  3. Validate in scripts: only pass the flag when the value is a positive integer

Example fix

# before
remix test --retries 0
# after
remix test --retries 1
Defensive patterns

Strategy: validation

Validate before calling

function positiveIntOrOmit(raw: string | undefined): string[] {
  let n = raw === undefined ? NaN : Number(raw);
  return Number.isInteger(n) && n >= 1 ? [String(n)] : [];
}
run(['remix','test','--retries', ...positiveIntOrOmit(process.env.RETRIES)])

Type guard

function isPositiveIntegerFlag(v: string): boolean {
  let n = Number(v);
  return v.trim() !== '' && Number.isInteger(n) && n >= 1;
}

Prevention

When it happens

Trigger: Passing `0`, `-1`, `2.5`, or empty string to a positive-integer option of `remix test` (e.g. retries or worker counts).

Common situations: CI configs computing counts that land on 0; division in scripts producing decimals; empty env vars expanding into the flag.

Understand the failure class

Background: Invalid option value errors: "must be one of", "is not a valid", and "only allows" failures explained — this error's family across 23 libraries.

Related errors


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