{"record":{"id":"6ad2cdb5181267b2","repo":"can1357/oh-my-pi","slug":"native-spelling-thread-stopped","errorCode":null,"errorMessage":"native spelling thread stopped","messagePattern":"native spelling thread stopped","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/pi-natives/src/spelling.rs","lineNumber":75,"sourceCode":"\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()\n\t\t\t.await\n\t\t\t.map_err(|_| Error::new(Status::GenericFailure, \"native spelling thread stopped\"))?\n\t}\n\n\tfn ns_range(start: u32, length: u32) -> Result<NSRange> {\n\t\tOk(NSRange {\n\t\t\tlocation: usize::try_from(start)\n\t\t\t\t.map_err(|_| Error::new(Status::InvalidArg, \"spelling range start is too large\"))?,\n\t\t\tlength:   usize::try_from(length)\n\t\t\t\t.map_err(|_| Error::new(Status::InvalidArg, \"spelling range length is too large\"))?,\n\t\t})\n\t}\n\n\tpub fn check(text: &str) -> Result<Vec<SpellingRange>> {\n\t\tlet checker = checker()?;\n\t\tlet text = NSString::from_str(text);","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-natives/src/spelling.rs#L57-L93","documentation":"All native spelling work is dispatched to one dedicated, lazily spawned thread (`pi-native-spelling`) through a `flume` channel. This error is raised when the job cannot be queued (the sender's receiver half was dropped, i.e. the thread died) or the reply never arrives (the receiver was dropped), both signs the worker thread is gone. The process's spelling subsystem is unusable after this.","triggerScenarios":"Calling any async macOS spelling export after the dedicated spelling thread has terminated — e.g. the thread panicked while executing a previous job (the `while let Ok(job) = receiver.recv()` loop then ends on channel close, but a panic unwinds and kills the thread), or process teardown dropped the channel while an in-flight call awaited its reply.","commonSituations":"A previous `checkString:` call panicked inside AppKit on the worker thread, killing the loop; the host process is shutting down while a spelling promise is still pending; a latent panic in a job closure leaves every subsequent spelling call failing.","solutions":["Treat the error as terminal for the spelling feature: catch it and permanently fall back to a JS spell checker for the session (retrying cannot revive the thread)","Find and fix the panic that killed the worker thread — look for panics logged from the `pi-native-spelling` thread before this error appeared","Avoid calling spelling APIs during process shutdown when the runtime is tearing down threads","Restart the host process if the spelling feature is essential; the thread is respawned lazily on next launch"],"exampleFix":"// before\nconst guesses = await macOSSpellingGuesses(word, 0, word.length);\n// after\nlet guesses = [];\ntry {\n  guesses = await macOSSpellingGuesses(word, 0, word.length);\n} catch (err) {\n  if (String(err?.message).includes('native spelling thread stopped')) {\n    spellingBroken = true; // stop calling native spelling this session\n  }\n  guesses = jsFallbackGuesses(word);\n}","handlingStrategy":"try-catch","validationCode":"let nativeSpellingAlive = true; // flip to false permanently on this error\nasync function guardedSpellingCall(fn, fallback) {\n  if (!nativeSpellingAlive) return fallback();\n  try { return await fn(); }\n  catch (err) {\n    if (String(err?.message).includes('native spelling thread stopped')) {\n      nativeSpellingAlive = false;\n      return fallback();\n    }\n    throw err;\n  }\n}","typeGuard":"function isSpellingThreadStopped(err) {\n  return err instanceof Error && err.message === 'native spelling thread stopped';\n}","tryCatchPattern":"try {\n  result = await macOSAutocorrectWord(word, start, length);\n} catch (err) {\n  if (isSpellingThreadStopped(err)) {\n    disableNativeSpelling(); // session-wide flag; never retry natively\n    return jsAutocorrect(word);\n  }\n  throw err;\n}","preventionTips":["Mark the spelling feature dead for the session after this error — the thread never comes back","Investigate any panic originating on the pi-native-spelling thread; it kills all subsequent calls","Avoid issuing spelling calls during process shutdown or worker teardown","Keep a JS-side spell-check fallback so the feature degrades instead of throwing to the user"],"tags":["thread","worker-thread","panic","macos","concurrency"],"backgroundTag":"worker-thread-stopped","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}