{"record":{"id":"f88098587d61306c","repo":"can1357/oh-my-pi","slug":"invalidarg","errorCode":"InvalidArg","errorMessage":"error.to_string()","messagePattern":"error\\.to_string\\(\\)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/pi-natives/src/js.rs","lineNumber":234,"sourceCode":"pub fn utf8(value: JsString<'_>) -> Result<Utf8> {\n\tlet raw = value.value();\n\tARENA.with(|arena| {\n\t\tlet (start, avail) = arena.tail(1);\n\t\tif avail >= 2 {\n\t\t\t// SAFETY: `start..start + avail` is past every committed range.\n\t\t\tlet ptr = unsafe { arena.base().add(start) };\n\t\t\tlet mut written = 0;\n\t\t\t// SAFETY: `raw` is a JS string owned by the live callback; Node-API\n\t\t\t// writes at most `avail - 1` bytes plus a NUL into the free tail.\n\t\t\tlet status = unsafe {\n\t\t\t\tsys::napi_get_value_string_utf8(raw.env, raw.value, ptr.cast(), avail, &mut written)\n\t\t\t};\n\t\t\tnapi::check_status!(status, \"Failed to read JavaScript string\")?;\n\t\t\tif written < avail - 1 {\n\t\t\t\t// SAFETY: Node-API initialised `written` bytes at `ptr`.\n\t\t\t\tlet bytes = unsafe { slice::from_raw_parts(ptr, written) };\n\t\t\t\tif let Err(error) = str::from_utf8(bytes) {\n\t\t\t\t\treturn Err(Error::new(Status::InvalidArg, error.to_string()));\n\t\t\t\t}\n\t\t\t\tarena.commit(start, written);\n\t\t\t\treturn Ok(Utf8(TextRepr::Scratch { ptr: NonNull::new(ptr).unwrap(), len: written }));\n\t\t\t}\n\t\t}\n\n\t\tlet mut len = 0;\n\t\t// SAFETY: a null buffer asks Node-API for the byte length only.\n\t\tlet status = unsafe {\n\t\t\tsys::napi_get_value_string_utf8(raw.env, raw.value, ptr::null_mut(), 0, &mut len)\n\t\t};\n\t\tnapi::check_status!(status, \"Failed to measure JavaScript string\")?;\n\t\tlet mut buf: Vec<u8> = Vec::with_capacity(len + 1);\n\t\tlet mut written = 0;\n\t\t// SAFETY: `buf` holds the measured length plus the NUL slot.\n\t\tlet status = unsafe {\n\t\t\tsys::napi_get_value_string_utf8(\n\t\t\t\traw.env,","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-natives/src/js.rs#L216-L252","documentation":"pi-natives' `utf8` helper copies a JS string into a Rust &str via napi_get_value_string_utf8 using a thread-local scratch arena. After copying, it validates the bytes with str::from_utf8; if Node-API returned bytes that aren't valid UTF-8, it returns a Node-API Error with Status::InvalidArg carrying the UTF-8 validation message. This should be practically unreachable (Node-API guarantees UTF-8 output), so hitting it signals arena/pointer misuse or a corrupt napi buffer rather than bad JS input.","triggerScenarios":"Calling the exposed native binding that takes a JS string argument when the fast-path arena copy yields invalid UTF-8 — e.g. after an arena overflow/miscommit bug, memory corruption, or a mismatched native binary that violates the napi_get_value_string_utf8 contract. Surfaces in JS as an Error with code 'InvalidArg'.","commonSituations":"A stale or ABI-mismatched pi-natives .node binary (native addon rebuilt against different Rust/Node versions) being loaded; running under a patched or exotic Node-API shim; reproducing after a partial arena commit corrupted the scratch buffer.","solutions":["Rebuild the native addon from source (`cargo build -p pi-natives` / the package's install script) to eliminate binary/ABI mismatch, then reload the host process.","Update to the latest pi-natives version — if this fires at all it is a library bug; check the changelog/issues for arena fixes and upgrade.","Restart the Node/Bun process to reset the thread-local arena; if the error is transient it points at scratch-buffer corruption during a long-lived process.","If it persists, reduce the input string to a minimal reproduction (length, code points) and file a bug — the InvalidArg is raised on library-internal bytes, not on user input."],"exampleFix":"// before: stale prebuilt addon triggers invalid UTF-8 from the arena fast path\nError [InvalidArg]: invalid utf-8 sequence of 1 bytes from index 12\n\n// after: rebuild the native module to match the current ABI\n$ bun run build:natives   # or: cargo build --release -p pi-natives\n$ node -e \"require('./pi-natives')\"  # restart the host process","handlingStrategy":"try-catch","validationCode":"function isUsableNativeAddon(mod) {\n  return typeof mod === 'object' && mod !== null && typeof mod.binding === 'object';\n}","typeGuard":null,"tryCatchPattern":"let result;\ntry {\n  result = nativeCallWithString(str);\n} catch (err) {\n  if (err instanceof Error && err.code === 'InvalidArg') {\n    // Library-internal UTF-8 validation failure: rebuild addon / restart process\n    console.error('pi-natives returned InvalidArg; rebuild the native addon and retry');\n  } else {\n    throw err;\n  }\n}","preventionTips":["Rebuild pi-natives whenever Rust toolchain, Node, or Bun versions change so the .node binary matches the current ABI.","Restart long-lived processes periodically if they invoke native bindings heavily, so a scratch-arena bug can't accumulate.","Keep pi-natives up to date; this error signals a library bug — report minimal reproductions upstream."],"tags":["napi","native-addon","utf8","memory"],"backgroundTag":"invalid-utf8-napi","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}