can1357/oh-my-pi · error · Error
native spelling thread stopped
Error message
native spelling thread stopped
What it means
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.
Source
Thrown at crates/pi-natives/src/spelling.rs:75
fn checker() -> Result<Retained<NSSpellChecker>> {
if !*APP_KIT_LOADED {
return Err(Error::new(Status::GenericFailure, "failed to initialize AppKit"));
}
let checker = NSSpellChecker::sharedSpellChecker();
checker.setAutomaticallyIdentifiesLanguages(true);
Ok(checker)
}
pub async fn run<T>(work: impl FnOnce() -> Result<T> + Send + 'static) -> Result<T>
where
T: Send + 'static,
{
let (reply, result) = flume::bounded(1);
SPELLING_THREAD
.send(Box::new(move || {
let _ = reply.send(work());
}))
.map_err(|_| Error::new(Status::GenericFailure, "native spelling thread stopped"))?;
result
.recv_async()
.await
.map_err(|_| Error::new(Status::GenericFailure, "native spelling thread stopped"))?
}
fn ns_range(start: u32, length: u32) -> Result<NSRange> {
Ok(NSRange {
location: usize::try_from(start)
.map_err(|_| Error::new(Status::InvalidArg, "spelling range start is too large"))?,
length: usize::try_from(length)
.map_err(|_| Error::new(Status::InvalidArg, "spelling range length is too large"))?,
})
}
pub fn check(text: &str) -> Result<Vec<SpellingRange>> {
let checker = checker()?;
let text = NSString::from_str(text);View on GitHub (pinned to 9690622007)
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
Example fix
// before
const guesses = await macOSSpellingGuesses(word, 0, word.length);
// after
let guesses = [];
try {
guesses = await macOSSpellingGuesses(word, 0, word.length);
} catch (err) {
if (String(err?.message).includes('native spelling thread stopped')) {
spellingBroken = true; // stop calling native spelling this session
}
guesses = jsFallbackGuesses(word);
} Defensive patterns
Strategy: try-catch
Validate before calling
let nativeSpellingAlive = true; // flip to false permanently on this error
async function guardedSpellingCall(fn, fallback) {
if (!nativeSpellingAlive) return fallback();
try { return await fn(); }
catch (err) {
if (String(err?.message).includes('native spelling thread stopped')) {
nativeSpellingAlive = false;
return fallback();
}
throw err;
}
} Type guard
function isSpellingThreadStopped(err) {
return err instanceof Error && err.message === 'native spelling thread stopped';
} Try / catch
try {
result = await macOSAutocorrectWord(word, start, length);
} catch (err) {
if (isSpellingThreadStopped(err)) {
disableNativeSpelling(); // session-wide flag; never retry natively
return jsAutocorrect(word);
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- modified change requires peer root
- failed to initialize AppKit
- spelling range start is too large
- spelling range length is too large
- native task `{tag}` panicked: {message}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6ad2cdb5181267b2.
Report an issue: GitHub.