redis/node-redis · error · Error

HIMPORT PREPARE/DISCARD/DISCARDALL are not supported inside…

Error message

HIMPORT PREPARE/DISCARD/DISCARDALL are not supported inside MULTI/pipeline; call them on the client before the transaction

What it means

The client maintains a fieldset registry for HIMPORT transparency. MULTI/pipeline only stores raw args, so a HIMPORT PREPARE / DISCARD / DISCARDALL queued in a transaction would bypass the registry and silently diverge client state from server state. The client rejects these at the exec funnel. HIMPORT SET is allowed inside MULTI because it mutates no registry state.

Solutions

  1. Issue HIMPORT PREPARE on the client directly (outside MULTI) before building the transaction.
  2. Issue HIMPORT DISCARD/DISCARDALL on the client after the transaction completes.
  3. Keep only HIMPORT SET inside the multi block.

Example fix

// before
const m = client.multi();
m.addCommand(['HIMPORT', 'PREPARE', fieldset]);
m.hImportSet(fieldset, 'k', v);
await m.exec();

// after
await client.hImportPrepare(fieldset);
const m = client.multi();
m.hImportSet(fieldset, 'k', v);
await m.exec();
Defensive patterns

Strategy: validation

Validate before calling

const HIMPORT_SESSION = new Set(['PREPARE', 'DISCARD', 'DISCARDALL']);

function assertNoHimportInMulti(commands) {
  for (const { args } of commands) {
    if (String(args[0]).toUpperCase() === 'HIMPORT' && HIMPORT_SESSION.has(String(args[1]).toUpperCase())) {
      throw new Error('HIMPORT PREPARE/DISCARD/DISCARDALL cannot be queued in MULTI; call them on the client directly');
    }
  }
}

Prevention

When it happens

Trigger: Queuing HIMPORT PREPARE/DISCARD/DISCARDALL via multi.addCommand(['HIMPORT','PREPARE',...]) and then exec(), or via typed multi methods, then calling exec().

Common situations: Trying to batch HIMPORT session setup inside a transaction for perceived atomicity; refactoring existing HIMPORT calls into a multi block without realising the registry implication.

Related errors


AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11). Data as JSON: /api/errors/a8b5272fcc3c35d9. Report an issue: GitHub.

Appendix: source

Thrown at packages/client/lib/client/index.ts:52

import { ASKING_CMD } from '../commands/ASKING';

const noop = () => {};

const HIMPORT_SESSION_SUBCOMMANDS = new Set(['PREPARE', 'DISCARD', 'DISCARDALL']);

/**
 * MULTI/pipeline stores raw args only, so the HIMPORT transparency hook never sees these
 * commands — a PREPARE/DISCARD executed that way would silently diverge the client registry
 * from server state (a fieldset the registry doesn't know about, or a discarded one it
 * would resurrect via lazy prepare). Rejected client-side at the exec funnel, which covers
 * both the typed multi methods and raw `multi.addCommand(...)`. HIMPORT SET stays allowed:
 * it mutates no registry state (the fieldset must already exist on the carrying connection).
 */
function assertNoHimportSessionCommands(commands: Array<RedisMultiQueuedCommand>) {
  for (const { args } of commands) {
    if (String(args[0]).toUpperCase() !== 'HIMPORT') continue;
    if (HIMPORT_SESSION_SUBCOMMANDS.has(String(args[1]).toUpperCase())) {
      throw new Error(
        'HIMPORT PREPARE/DISCARD/DISCARDALL are not supported inside MULTI/pipeline; call them on the client before the transaction'
      );
    }
  }
}

export interface RedisClientOptions<
  M extends RedisModules = RedisModules,
  F extends RedisFunctions = RedisFunctions,
  S extends RedisScripts = RedisScripts,
  RESP extends RespVersions = 3,
  TYPE_MAPPING extends TypeMapping = TypeMapping,
  SocketOptions extends RedisSocketOptions = RedisSocketOptions
> extends CommanderConfig<M, F, S, RESP> {
  /**
   * `redis[s]://[[username][:password]@][host][:port][/db-number]`
   * See [`redis`](https://www.iana.org/assignments/uri-schemes/prov/redis) and [`rediss`](https://www.iana.org/assignments/uri-schemes/prov/rediss) IANA registration for more details
   */

View on GitHub (pinned to 90fd0652bc)