{"record":{"id":"359a7618c7125ae6","repo":"can1357/oh-my-pi","slug":"failed-to-initialize-appkit","errorCode":null,"errorMessage":"failed to initialize AppKit","messagePattern":"failed to initialize AppKit","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/pi-natives/src/spelling.rs","lineNumber":59,"sourceCode":"\t\t\t})\n\t\t\t.expect(\"failed to spawn the native spelling thread\");\n\t\tsender\n\t});\n\tstatic APP_KIT_LOADED: LazyLock<bool> = LazyLock::new(|| {\n\t\t// SAFETY: AppKit documents `NSApplicationLoad` as process-global and\n\t\t// idempotent; `LazyLock` guarantees this process calls it at most once.\n\t\tunsafe { NSApplicationLoad() }\n\t});\n\tconst NS_NOT_FOUND: usize = isize::MAX as usize;\n\n\t#[link(name = \"AppKit\", kind = \"framework\")]\n\tunsafe extern \"C\" {\n\t\tfn NSApplicationLoad() -> bool;\n\t}\n\n\tfn checker() -> Result<Retained<NSSpellChecker>> {\n\t\tif !*APP_KIT_LOADED {\n\t\t\treturn Err(Error::new(Status::GenericFailure, \"failed to initialize AppKit\"));\n\t\t}\n\t\tlet checker = NSSpellChecker::sharedSpellChecker();\n\t\tchecker.setAutomaticallyIdentifiesLanguages(true);\n\t\tOk(checker)\n\t}\n\n\tpub async fn run<T>(work: impl FnOnce() -> Result<T> + Send + 'static) -> Result<T>\n\twhere\n\t\tT: Send + 'static,\n\t{\n\t\tlet (reply, result) = flume::bounded(1);\n\t\tSPELLING_THREAD\n\t\t\t.send(Box::new(move || {\n\t\t\t\tlet _ = reply.send(work());\n\t\t\t}))\n\t\t\t.map_err(|_| Error::new(Status::GenericFailure, \"native spelling thread stopped\"))?;\n\t\tresult\n\t\t\t.recv_async()","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-natives/src/spelling.rs#L41-L77","documentation":"On macOS, the spelling module initializes AppKit exactly once per process via `NSApplicationLoad()` and caches the result in a `LazyLock`. If that call returns false, every spelling API (`check`, `completions`, `guesses`, `correction`) fails with this `GenericFailure` because `NSSpellChecker` requires an initialized AppKit. On non-macOS builds these APIs are no-ops and never throw this.","triggerScenarios":"`macOSCheckSpelling`, `macOSCompleteWord`, `macOSAutocorrectWord`, or `macOSSpellingGuesses` called in a process where `NSApplicationLoad()` failed on first use — most commonly a headless or non-app context (plain CLI daemon, SSH session without a WindowServer connection, CI runner, embedded/JS-only runtime) on macOS.","commonSituations":"Running a GUI-dependent native module inside a headless test runner on macOS CI; launching the app over SSH where no Aqua session exists; calling the API before any NSApplication setup in a bare `node` script; sandboxed environments where AppKit cannot initialize.","solutions":["Check the exported `macOSSpellCheckerAvailable()` and treat false/throwing as 'no spelling support', degrading to a JS-side fallback (e.g. a wordlist)","Ensure the process runs inside a logged-in Aqua GUI session on macOS, not over SSH or in a headless CI job","Call the spelling APIs only after basic AppKit initialization in the host app (main NSApplication setup) if embedding","Wrap calls in try/catch and fall back to a non-native spell checker when this error surfaces"],"exampleFix":"// before\nconst ranges = await macOSCheckSpelling(text);\n// after\nlet ranges = [];\nif (macOSSpellCheckerAvailable()) {\n  try { ranges = await macOSCheckSpelling(text); }\n  catch { ranges = []; } // headless AppKit: fall back\n}\nranges = ranges.length ? ranges : jsFallbackCheck(text);","handlingStrategy":"fallback","validationCode":"if (!macOSSpellCheckerAvailable()) {\n  // non-macOS or unavailable: skip native spelling entirely\n  return [];\n}\n// additionally, detect headless AppKit lazily via a probe call wrapped in try/catch","typeGuard":"function nativeSpellingSupported() {\n  return typeof macOSSpellCheckerAvailable === 'function' && macOSSpellCheckerAvailable() === true;\n}","tryCatchPattern":"let ranges;\ntry {\n  ranges = await macOSCheckSpelling(text);\n} catch (err) {\n  if (err?.code === 'GenericFailure' && String(err?.message).includes('failed to initialize AppKit')) {\n    return jsFallbackCheckSpelling(text); // headless environment\n  }\n  throw err;\n}","preventionTips":["Gate native spelling calls behind macOSSpellCheckerAvailable()","Expect failure in SSH/headless/CI macOS sessions and keep a JS spell-check fallback wired","Call spelling APIs only from GUI-context processes with a WindowServer session","Cache the degraded state after the first AppKit error instead of retrying every call"],"tags":["macos","appkit","headless","napi","spelling"],"backgroundTag":"appkit-initialization-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}