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

assertNoHimportSessionCommands runs at the exec/pipeline funnel (_executeMulti / _executePipeline) and rejects any queued HIMPORT PREPARE, DISCARD, or DISCARDALL. MULTI/pipeline only stores raw args, so the HIMPORT transparency hook never runs for these — executing them in a transaction would silently desync the client-side fieldset registry from the server's session state (a fieldset the registry doesn't know about, or a discarded one it would resurrect via lazy prepare). HIMPORT SET is allowed because it mutates no registry state.

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 bb5beb5657)

Solutions

  1. Call HIMPORT PREPARE / DISCARD / DISCARDALL on the client directly (not inside multi/pipeline) before starting the transaction.
  2. Move only HIMPORT SET (and other non-session HIMPORT subcommands) into the MULTI/pipeline block.
  3. If you queue commands generically, filter HIMPORT session subcommands out of pipeline/multi batches.

Example fix

// before
await client.multi()
  .hImportPrepare('myset')
  .hImportSet('myset', data)
  .exec();

// after
await client.hImportPrepare('myset');
await client.multi()
  .hImportSet('myset', data)
  .exec();
Defensive patterns

Strategy: validation

Validate before calling

const HIMPORT_SESSION = new Set(['PREPARE', 'DISCARD', 'DISCARDALL']);
function isHimportSessionCommand(args) {
  return String(args[0]).toUpperCase() === 'HIMPORT' && HIMPORT_SESSION.has(String(args[1]).toUpperCase());
}
// filter your pipeline/multi batch:
const safe = commands.filter(c => !isHimportSessionCommand(c.args));

Type guard

function isHimportSessionSubcommand(args: ReadonlyArray<unknown>): boolean {
  return String(args[0]).toUpperCase() === 'HIMPORT' &&
    new Set(['PREPARE', 'DISCARD', 'DISCARDALL']).has(String(args[1]).toUpperCase());
}

Try / catch

try {
  await multi.exec();
} catch (err) {
  if (err instanceof Error && /HIMPORT PREPARE\/DISCARD/.test(err.message)) {
    // move HIMPORT session commands out of the transaction and retry
  } else throw err;
}

Prevention

When it happens

Trigger: `client.multi().hImportPrepare(...).exec()`; `client.multi().addCommand(['HIMPORT','PREPARE',...]).exec()`; building a pipeline that includes HIMPORT DISCARD/DISCARDALL; calling the typed hImportDiscard/hImportDiscardAll/hImportPrepare methods through a multi/transaction chain.

Common situations: Refactoring HIMPORT workflow to run inside a transaction for atomicity; copy-pasting a sequence of HIMPORT calls into a multi block; a generic pipeline builder that indiscriminately queues every queued command.

Related errors


AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03). Data as JSON: /data/errors/a8b5272fcc3c35d9.json. Report an issue: GitHub.