janhq/jan · critical

clone log file

Error message

clone log file

What it means

This is a panic (.expect) from File::try_clone() on the log file handle inside spawn_detached(). try_clone() duplicates the underlying file descriptor so one copy can be used for the child's stdout and the original for stderr. It fails when the OS cannot duplicate the descriptor — fd exhaustion (EMFILE), the file handle was already closed, or an I/O error at the OS level.

Source

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

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

    // Resolve log file path
    let log_path: PathBuf = args.log.as_deref()
        .map(PathBuf::from)
        .unwrap_or_else(|| cli_get_data_folder().join("logs").join("serve.log"));

    if let Some(parent) = log_path.parent() {
        let _ = std::fs::create_dir_all(parent);
    }

    let log_file = std::fs::OpenOptions::new()
        .create(true).append(true).open(&log_path)
        .unwrap_or_else(|e| { eprintln!("Cannot open log file {}: {e}", log_path.display()); std::process::exit(1); });
    let log_out = log_file.try_clone().expect("clone log file");

    let mut cmd = std::process::Command::new(&exe);
    cmd.args(&argv)
        .stdin(std::process::Stdio::null())
        .stdout(log_out)
        .stderr(log_file);

    // Detach from the current terminal session on Unix
    #[cfg(unix)]
    {
        use std::os::unix::process::CommandExt;
        unsafe {
            cmd.pre_exec(|| {
                nix::unistd::setsid()
                    .map(|_| ())
                    .map_err(|e| std::io::Error::other(e.to_string()))
            });
        }

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Raise the file descriptor limit: `ulimit -n 65536` before launching jan.
  2. Ensure the log directory is on a writable, mounted filesystem.
  3. Check for SELinux/AppArmor policies that may deny dup() syscalls.
  4. Replace .expect with a fallback that re-opens the file or uses /dev/null.

Example fix

// before
let log_out = log_file.try_clone().expect("clone log file");

// after
let log_out = log_file.try_clone().unwrap_or_else(|e| {
    eprintln!("Warning: could not clone log fd ({e}); re-opening file");
    std::fs::OpenOptions::new()
        .create(true).append(true).open(&log_path)
        .unwrap_or_else(|_| std::fs::File::create("/dev/null").unwrap())
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Check fd limits before cloning
#[cfg(unix)]
fn check_fd_headroom() -> bool {
    use std::mem;
    let mut rlim = libc::rlimit { rlim_cur: 0, rlim_max: 0 };
    unsafe {
        if libc::getrlimit(libc::RLIMIT_NOFILE, &mut rlim) != 0 {
            return true; // can't check, assume ok
        }
    }
    // If soft limit is very low, cloning may fail
    rlim.rlim_cur > 64
}

if !check_fd_headroom() {
    eprintln!("Warning: low file descriptor limit; log file clone may fail.");
}

Try / catch

// Replace .expect with graceful handling
let log_out = log_file.try_clone().unwrap_or_else(|e| {
    eprintln!("Warning: could not clone log file handle ({e}); using same handle for stdout+stderr");
    // Fallback: try to re-open the file, or use a null sink
    std::fs::File::create("/dev/null").unwrap_or(log_file.try_clone().unwrap())
});

Prevention

When it happens

Trigger: Process has hit the open file descriptor limit (ulimit -n). The log file was on a filesystem that was unmounted or became read-only after opening. Resource limits inside a container (cgroup pids/fd limits). The file handle was consumed/dropped by another code path before try_clone runs.

Common situations: Long-running CLI sessions with many open files hitting ulimit defaults (1024). Running inside Docker with restrictive --ulimit nofile settings. Disk full or filesystem remounted read-only. SELinux/AppArmor denying dup() syscall.

Related errors


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