can1357/oh-my-pi · error · Error

failed to initialize AppKit

Error message

failed to initialize AppKit

What it means

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.

Source

Thrown at crates/pi-natives/src/spelling.rs:59

			})
			.expect("failed to spawn the native spelling thread");
		sender
	});
	static APP_KIT_LOADED: LazyLock<bool> = LazyLock::new(|| {
		// SAFETY: AppKit documents `NSApplicationLoad` as process-global and
		// idempotent; `LazyLock` guarantees this process calls it at most once.
		unsafe { NSApplicationLoad() }
	});
	const NS_NOT_FOUND: usize = isize::MAX as usize;

	#[link(name = "AppKit", kind = "framework")]
	unsafe extern "C" {
		fn NSApplicationLoad() -> bool;
	}

	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()

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the exported `macOSSpellCheckerAvailable()` and treat false/throwing as 'no spelling support', degrading to a JS-side fallback (e.g. a wordlist)
  2. Ensure the process runs inside a logged-in Aqua GUI session on macOS, not over SSH or in a headless CI job
  3. Call the spelling APIs only after basic AppKit initialization in the host app (main NSApplication setup) if embedding
  4. Wrap calls in try/catch and fall back to a non-native spell checker when this error surfaces

Example fix

// before
const ranges = await macOSCheckSpelling(text);
// after
let ranges = [];
if (macOSSpellCheckerAvailable()) {
  try { ranges = await macOSCheckSpelling(text); }
  catch { ranges = []; } // headless AppKit: fall back
}
ranges = ranges.length ? ranges : jsFallbackCheck(text);
Defensive patterns

Strategy: fallback

Validate before calling

if (!macOSSpellCheckerAvailable()) {
  // non-macOS or unavailable: skip native spelling entirely
  return [];
}
// additionally, detect headless AppKit lazily via a probe call wrapped in try/catch

Type guard

function nativeSpellingSupported() {
  return typeof macOSSpellCheckerAvailable === 'function' && macOSSpellCheckerAvailable() === true;
}

Try / catch

let ranges;
try {
  ranges = await macOSCheckSpelling(text);
} catch (err) {
  if (err?.code === 'GenericFailure' && String(err?.message).includes('failed to initialize AppKit')) {
    return jsFallbackCheckSpelling(text); // headless environment
  }
  throw err;
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/359a7618c7125ae6. Report an issue: GitHub.