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
- Run `gleam run` as a subprocess (spawn the gleam binary) instead of calling the Rust function twice in-process.
- If embedding, don't install your own ctrlc handler before calling this code — let gleam own it, or fork.
- For maintainers: match on ctrlc::Error::MultipleHandlers and continue instead of expect, since an existing handler still prevents the default exit.
- 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
- Invoke `gleam run` as a subprocess rather than calling the crate's command function more than once per process.
- Only one component per process may own ctrlc::set_handler — never install your own before calling gleam code.
- In test harnesses, give each test its own process (or use one cargo test per process) instead of re-entering command().
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
- Error setting Ctrl-C handler
- `panic` expression evaluated.
- BEAM compiler instance exited: {status}
- Writing warning to stderr
- Final result error writing
AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17).
Data as JSON: /api/errors/3d65c16aed00b6dc.
Report an issue: GitHub.