napi-rs/napi-rs · error

InvalidArg

InvalidArg

Error message

CurrentThread host registration was not reserved or was already consumed

What it means

`claim_host_registration_id` verifies that the (high, low) word pair passed from JavaScript corresponds to an id previously returned by `reserve_current_thread_host_registration` and still present in `RESERVED_HOST_REGISTRATIONS`; if the id was never reserved or was already removed (each reservation is single-use — claimed on registration), it raises this `InvalidArg` error (crates/async-runtime/src/adapter.rs:342). It is a one-time-use token check guarding task and timer host installation.

Solutions

  1. Reserve a fresh token immediately before each registration: `const reg = reserveCurrentThreadHostRegistration()` then pass exactly its high/low once.
  2. Never reuse a registration object after a successful `registerCurrentThreadTaskHost`/`registerTimerHost` call — request a new one.
  3. Verify the high/low values are passed unmodified (no re-serialization or partial copies) from the reserve call to the registration call.
  4. If an installation call failed midway, discard the token and reserve a new one instead of retrying with the same words.

Example fix

// before (token reused)
const reg = reserveCurrentThreadHostRegistration();
registerCurrentThreadTaskHost(env, reg.high, reg.low, host);
registerTimerHost(env, reg.high, reg.low, timerHost); // InvalidArg

// after
const reg1 = reserveCurrentThreadHostRegistration();
registerCurrentThreadTaskHost(env, reg1.high, reg1.low, host);
const reg2 = reserveCurrentThreadHostRegistration();
registerTimerHost(env, reg2.high, reg2.low, timerHost);
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof reg !== 'object' || reg === null ||
    typeof reg.high !== 'number' || typeof reg.low !== 'number') {
  throw new TypeError('registration must be { high: number, low: number } from reserveCurrentThreadHostRegistration');
}

Type guard

function isValidRegistration(reg): reg is { high: number, low: number } {
  return typeof reg === 'object' && reg !== null &&
    Number.isInteger(reg.high) && Number.isInteger(reg.low);
}

Try / catch

try {
  registerCurrentThreadTaskHost(env, reg.high, reg.low, host);
} catch (e) {
  if (e.message.includes('not reserved or was already consumed')) {
    const fresh = reserveCurrentThreadHostRegistration(); // re-reserve once, do not loop
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling `registerCurrentThreadTaskHost` or `registerTimerHost` with a registration that was never obtained from `reserveCurrentThreadHostRegistration`, with fabricated/zero high-low words, or with a registration already consumed by a previous successful registration call.

Common situations: Reusing a registration token to install a second task/timer; caching the registration across attempts and retrying after a failed installation; skipping the reserve step and passing arbitrary {high, low} values; wiring mistakes where the reserved token belongs to a different thread's host.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of napi-rs/napi-rs@39bd1205e4 (2026-09-13). Data as JSON: /api/errors/ce17265e9a6e442f. Report an issue: GitHub.

Appendix: source

Thrown at crates/async-runtime/src/adapter.rs:342

      )
    })?;
  RESERVED_HOST_REGISTRATIONS
    .lock()
    .unwrap_or_else(std::sync::PoisonError::into_inner)
    .insert(id);
  Ok(id)
}

fn claim_host_registration_id(registration_high: u32, registration_low: u32) -> napi::Result<u64> {
  let id = BindingHostRegistration::id(registration_high, registration_low);
  if RESERVED_HOST_REGISTRATIONS
    .lock()
    .unwrap_or_else(std::sync::PoisonError::into_inner)
    .remove(&id)
  {
    Ok(id)
  } else {
    Err(napi::Error::new(
      napi::Status::InvalidArg,
      "CurrentThread host registration was not reserved or was already consumed",
    ))
  }
}

fn release_host_registration_id(id: u64) {
  RESERVED_HOST_REGISTRATIONS
    .lock()
    .unwrap_or_else(std::sync::PoisonError::into_inner)
    .remove(&id);
}

#[napi]
/// Reserve an exact CurrentThread host registration capability before either
/// task or timer installation performs side effects. The JavaScript package
/// validates the returned words and passes them back to one registration call.
pub fn reserve_current_thread_host_registration() -> napi::Result<BindingHostRegistration> {

View on GitHub (pinned to 39bd1205e4)