ruby/ruby · critical

Failed to create {log_file_path}: {err}

Error message

Failed to create {log_file_path}: {err}

What it means

When YJIT is given a log file (--yjit-log=/path or RubyVM::YJIT.enable(log: '/path')), option processing opens it with create+write+truncate before your program runs. If the open fails — missing parent directory, permission denied, path is a directory — the Rust code panics with this message, which aborts the whole ruby process. The panic embeds the underlying OS error string, so the exact cause is visible.

Source

Thrown at yjit/src/options.rs:360

                OPTIONS.log = Some(LogOutput::MemoryOnly);
                Log::init();
            },
            arg_value => {
                let log_file_path = if std::path::Path::new(arg_value).is_dir() {
                    format!("{arg_value}/yjit_{}.log", std::process::id())
                } else {
                    arg_value.to_string()
                };

                match File::options().create(true).write(true).truncate(true).open(&log_file_path) {
                    Ok(file) => {
                        use std::os::unix::io::IntoRawFd;
                        eprintln!("YJIT log: {log_file_path}");

                        unsafe { OPTIONS.log = Some(LogOutput::File(file.into_raw_fd())) }
                        Log::init()
                    }
                    Err(err) => panic!("Failed to create {log_file_path}: {err}"),
                }
            }
        },
        ("trace-exits", _) => unsafe {
            OPTIONS.gen_stats = true;
            OPTIONS.trace_exits = match opt_val {
                "" => Some(TraceExits::All),
                name => match Counter::get(name) {
                    Some(counter) => Some(TraceExits::Counter(counter)),
                    None => return None,
                },
            };
        },
        ("trace-exits-sample-rate", sample_rate) => unsafe {
            OPTIONS.gen_stats = true;
            if OPTIONS.trace_exits.is_none() {
                OPTIONS.trace_exits = Some(TraceExits::All);
            }

View on GitHub (pinned to 0e5b888e1c)

Solutions

  1. Create the parent directory first: mkdir -p the directory portion of the log path
  2. Use an absolute path to a known-writable location such as $TMPDIR for --yjit-log
  3. Read the errno embedded in the panic (Permission denied vs No such file or directory) and fix ownership/permissions accordingly
  4. Drop --yjit-log entirely if the log is optional

Example fix

# before
ruby --yjit --yjit-log=logs/yjit.log app.rb  # panics when logs/ is missing

# after
mkdir -p logs && ruby --yjit --yjit-log=logs/yjit.log app.rb
Defensive patterns

Strategy: validation

Validate before calling

# Guard before enabling the YJIT log
log_path = ENV['YJIT_LOG']
if log_path
  dir = File.dirname(File.expand_path(log_path))
  unless File.directory?(dir) && File.writable?(dir)
    require 'fileutils'
    FileUtils.mkdir_p(dir) rescue log_path = nil  # fall back to no log file
  end
end
RubyVM::YJIT.enable(log: log_path)

Try / catch

Not rescuable from Ruby: the failure is a Rust panic during option processing, which aborts the process. Prevent it by validating the path (directory exists and is writable) before passing --yjit-log / enable(log:), and fall back to stderr logging when validation fails.

Prevention

When it happens

Trigger: --yjit-log=/var/log/yjit.log without write permission; --yjit-log=out/yjit.log when out/ does not exist; a log path that points at a directory; a relative path resolved from a cwd that cannot host files (systemd, cron, container read-only fs).

Common situations: CI sandboxes with read-only or missing directories; wrapper scripts assuming a different working directory than the service manager uses; the application creating its log dir after Ruby startup has already processed YJIT options.

Related errors


AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21). Data as JSON: /api/errors/5218291cfded1c92. Report an issue: GitHub.