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
- Call HIMPORT PREPARE / DISCARD / DISCARDALL on the client directly (not inside multi/pipeline) before starting the transaction.
- Move only HIMPORT SET (and other non-session HIMPORT subcommands) into the MULTI/pipeline block.
- 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
- Call HIMPORT PREPARE/DISCARD/DISCARDALL on the client, not inside multi/pipeline.
- Only queue HIMPORT SET inside transactions.
- Filter HIMPORT session subcommands out of generic pipeline builders.
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
- ${errorIndexes.length} commands failed, see .replies and .er
- "arguments[${i}]" must be of type "string | Buffer", got ${t
- expirationRefreshRatio must be less than or equal to 1
- expirationRefreshRatio must be greater or equal to 0
- All statistics values must be non-negative
AI-assisted analysis of redis/node-redis@bb5beb5657 (2026-08-03).
Data as JSON: /data/errors/a8b5272fcc3c35d9.json.
Report an issue: GitHub.