The installed app couldn't load its own model — and why 'works in dev' was the tell
The operating-memory wedge worked perfectly in dev and was dead on every install. The cause was one missing argument: fastembed's cache defaulted to a path relative to the working directory, and a Finder-launched .app has cwd /.
The screenshot
The founder opened the installed app, hit ⌘K, and typed "최근에 누가 메일줬지" (who emailed me recently). Back came:
❌ 기억 검색에 실패했어요 — 잠시 후 다시 시도해 주세요.
(embedding model load failed: Failed to retrieve onnx/model.onnx)Every memory_answer died the same way. The entire operating-memory wedge — the product's whole reason to exist — was dead on the installed build. And it had passed 214 tests and worked flawlessly in pnpm tauri dev.
"Works in dev, dead on install" is a diagnosis, not a mystery
That gap is the whole clue. Something about the packaged runtime differs from the dev runtime. Two usual suspects: environment variables (we've been bitten there before) and — this time — the current working directory.
The error came from fastembed, the local embedding engine. It downloads a ~90MB ONNX model and caches it on disk. The model-load code:
TextEmbedding::try_new(
InitOptions::new(EmbeddingModel::ParaphraseMLMiniLML12V2)
.with_show_download_progress(true),
)No cache_dir. So fastembed uses its default: ./.fastembed_cache — relative to the process's working directory.
Diagnosing on the real machine, not guessing
Before changing anything, three checks on the founder's own Mac:
find ~ -name model.onnx→ nothing. The model was never successfully cached.curl -sI https://huggingface.co→HTTP/2 200. Network and DNS are fine — not a reachability problem.- fastembed 5.17.2 — modern, has
with_cache_dir.
So: the network's fine, the model just can't be stored. Why? A Finder/Dock-launched macOS .app runs with cwd /. fastembed tries to write /.fastembed_cache, which isn't writable, and the failed store surfaces as "Failed to retrieve onnx/model.onnx". tauri dev works only because its cwd is the writable crate directory, where ./.fastembed_cache happily appears.
The fix is one argument
let cache = dirs::data_dir()
.map(|d| d.join("DalkkakAI").join("models"))
.unwrap_or_else(|| PathBuf::from(".fastembed_cache"));
let _ = std::fs::create_dir_all(&cache);
TextEmbedding::try_new(
InitOptions::new(EmbeddingModel::ParaphraseMLMiniLML12V2)
.with_cache_dir(cache)
.with_show_download_progress(true),
)Pin the cache to the app data dir. The model downloads once and stays.
The lesson
A desktop app's working directory is not your project folder — it's /. Any dependency that defaults a cache, temp, or database path relative to cwd is a latent install-only bug that dev will never show you, because dev's cwd is writable. Pin every on-disk path the app writes to an explicit app-data location, as a reflex.
And the meta-lesson, again: the test suite was green and the dev app was green while the shipped product's core feature was a brick. You only find these by dogfooding the actual installed artifact.
This landed inside a wider usability pass — promoting the connect-a-source flow out of a dev-only console, turning the first-run onboarding from a terminal tutorial into "connect → ask → act", and making incoming work appear live across services by area instead of on a 30-second poll. But none of that matters if the engine can't load. First, make the model load.