janhq/jan · critical

cannot resolve current exe

Error message

cannot resolve current exe

What it means

This is a panic (.expect) from std::env::current_exe() inside spawn_detached(), the function that re-launches the jan CLI binary as a background server process. current_exe() can fail when the OS cannot resolve the running binary's path — on Linux this requires /proc/self/exe to be readable; on macOS it uses _NSGetExecutablePath; on Windows it queries GetModuleFileName. When it fails the process aborts immediately with no recovery.

Source

Thrown at src-tauri/src/bin/jan-cli.rs:720

        return all[selection_items[0].0].0.clone();
    }

    let labels: Vec<String> = selection_items.iter().map(|(_, label)| label.clone()).collect();

    let selection = dialoguer::Select::new()
        .with_prompt("Choose a model")
        .items(&labels)
        .default(0)
        .interact()
        .unwrap_or_else(|_| std::process::exit(1));

    all[selection_items[selection].0].0.clone()
}

// ── Detached spawn ─────────────────────────────────────────────────────────

fn spawn_detached(model_id: &str, args: &ServeArgs) {
    let exe = std::env::current_exe().expect("cannot resolve current exe");

    // Rebuild argv from ServeArgs fields so we have full control
    // (avoids needing to filter --detach/-d from the raw OS args).
    // Use --flag=value format throughout to avoid negative numbers being
    // misinterpreted as short flags (e.g. --n-gpu-layers -1 → -1 looks like a flag).
    let mut argv: Vec<String> = vec!["serve".into(), model_id.to_string()];
    if let Some(p) = &args.model_path { argv.push(format!("--model-path={p}")); }
    if let Some(b) = &args.bin        { argv.push(format!("--bin={b}")); }
    argv.push(format!("--port={}", args.port));
    if let Some(m) = &args.mmproj     { argv.push(format!("--mmproj={m}")); }
    if args.embedding                  { argv.push("--embedding".into()); }
    argv.push(format!("--timeout={}",      args.timeout));
    argv.push(format!("--n-gpu-layers={}", args.n_gpu_layers));
    argv.push(format!("--ctx-size={}",     args.ctx_size));
    argv.push(format!("--threads={}",      args.threads));
    if !args.api_key.is_empty()        { argv.push(format!("--api-key={}", args.api_key)); }
    if args.fit                        { argv.push("--fit".into()); }
    if args.verbose                    { argv.push("--verbose".into()); }

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Ensure /proc is mounted and /proc/self/exe is readable in container/namespace environments.
  2. Avoid deleting or replacing the jan binary while a serve session is running.
  3. For headless/containerized use, set the executable path explicitly via an env var instead of relying on current_exe.
  4. Run `jan serve` directly (non-detached) if detached spawn is not needed.

Example fix

// before
let exe = std::env::current_exe().expect("cannot resolve current exe");

// after
let exe = std::env::current_exe().unwrap_or_else(|e| {
    eprintln!("Warning: current_exe() failed ({e}), falling back to argv[0]");
    std::env::args().next().unwrap_or("jan".into()).into()
});
Defensive patterns

Strategy: fallback

Validate before calling

// Before spawning detached, verify the exe is resolvable
use std::env;

fn check_exe_resolvable() -> bool {
    env::current_exe().is_ok()
}

// In a test or startup check:
if !check_exe_resolvable() {
    eprintln!("Warning: current_exe() will fail in this environment; detached spawn will crash.");
}

Try / catch

// Replace .expect with a graceful fallback
let exe = match std::env::current_exe() {
    Ok(path) => path,
    Err(e) => {
        eprintln!("current_exe() failed: {e}");
        // Fallback: use argv[0] resolved against PATH
        let argv0 = std::env::args().next().unwrap_or_else(|| "jan".into());
        which::which(&argv0).unwrap_or_else(|_| PathBuf::from(argv0))
    }
};

Prevention

When it happens

Trigger: Running inside a container or chroot where /proc is not mounted (Linux). The executable file was deleted or replaced while the process is running (inode gone). Running under a statically-linked binary in a minimal namespace. Symlink chains that exceed MAX_SYMLINKS. Unusual sandbox environments that block procfs reads.

Common situations: CI runners with minimal container images (distroless, scratch). Dev containers or Docker dev environments without /proc. Package managers replacing the binary mid-run during an update. AppImage or Flatpak sandboxes restricting procfs.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/2fc79aa5b1740f89. Report an issue: GitHub.