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
- Reserve a fresh token immediately before each registration: `const reg = reserveCurrentThreadHostRegistration()` then pass exactly its high/low once.
- Never reuse a registration object after a successful `registerCurrentThreadTaskHost`/`registerTimerHost` call — request a new one.
- Verify the high/low values are passed unmodified (no re-serialization or partial copies) from the reserve call to the registration call.
- 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
- Always reserve immediately before registering; treat the token as single-use.
- Never retry a failed registration with the same token — reserve a new one.
- Pass high/low through unmodified; avoid cloning through serialization.
- Keep reserve/claim pairs on the same thread and in the same code path.
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
- {reason}
- async runtime generation task id space exhausted
- context
- {error}
- Failed to create the native CurrentThread task host
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)