gleam-lang/gleam · error

Error setting Ctrl-C handler

Error message

Error setting Ctrl-C handler

What it means

run.rs::command installs a no-op SIGINT handler before exec'ing the compiled program so Ctrl-C is delivered to the child Erlang VM instead of killing gleam. ctrlc::set_handler fails if a handler is already installed for this process (the crate enforces a single handler) or if the signal machinery can't initialize, and the expect("Error setting Ctrl-C handler") turns that into a panic. For a normal one-shot CLI process this is nearly unreachable; it bites code that embeds or re-invokes this command function in the same process.

Source

Thrown at compiler-cli/src/run.rs:39

#[derive(Debug, Clone, Copy)]
pub enum Which {
    Src,
    Test,
    Dev,
}

pub fn command(
    paths: &ProjectPaths,
    arguments: Vec<String>,
    target: Option<Target>,
    runtime: Option<Runtime>,
    module: Option<String>,
    which: Which,
    no_print_progress: bool,
) -> Result<(), Error> {
    // Don't exit on ctrl+c as it is used by child erlang shell
    ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler");
    let command = setup(
        paths,
        arguments,
        target,
        runtime,
        module,
        which,
        no_print_progress,
    )?;
    let status = ProjectIO::new().exec(command)?;
    std::process::exit(status);
}

pub fn setup(
    paths: &ProjectPaths,
    arguments: Vec<String>,
    target: Option<Target>,
    runtime: Option<Runtime>,

View on GitHub (pinned to 7e623aa83d)

Solutions

  1. Run `gleam run` as a subprocess (spawn the gleam binary) instead of calling the Rust function twice in-process.
  2. If embedding, don't install your own ctrlc handler before calling this code — let gleam own it, or fork.
  3. For maintainers: match on ctrlc::Error::MultipleHandlers and continue instead of expect, since an existing handler still prevents the default exit.
  4. Free the single-handler slot before invoking: drop/never set another handler in the host process.

Example fix

// before: harness sets a handler, then calls gleam's run
ctrlc::set_handler(|| println!("interrupt"))?;
gleam_cli::run::command(&paths, args, None, None, None, Which::Run, false)?; // panics

// after: exec gleam as a child process, keep your own handler
let status = std::process::Command::new("gleam").args(["run"]).status()?;
Defensive patterns

Strategy: fallback

Try / catch

// If you must host gleam in-process: probe once and fall back to a subprocess.
let ran_inline = std::panic::catch_unwind(|| {
    // first invocation of run::command(...) — installs the ctrl-c handler
})
.is_ok();
if !ran_inline {
    // handler slot taken (or other panic): fall back to spawning the gleam binary
    let _ = std::process::Command::new("gleam").arg("run").status();
}

Prevention

When it happens

Trigger: Calling run::command() twice in one process (integration tests, REPL-style wrappers), or running gleam as a library inside a host that already called ctrlc::set_handler. Exotic case: signal() blocked by a seccomp policy at startup.

Common situations: Custom test harnesses that exercise `gleam run` logic in-process; embedding the gleam CLI in a supervisor that manages its own Ctrl-C behavior; orchestrators that install signal handlers before running tasks.

Related errors


AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17). Data as JSON: /api/errors/3d65c16aed00b6dc. Report an issue: GitHub.