gleam-lang/gleam · error
Error setting Ctrl-C handler
Error message
Error setting Ctrl-C handler
What it means
shell.rs installs the same no-op Ctrl-C handler before launching the interactive `erl` shell (gleam shell), expecting success. ctrlc::set_handler returns an error when a handler already exists in this process or signal initialization fails, and the expect panics before the Erlang shell starts. As with run.rs this is essentially impossible for the CLI used normally (fresh process, one handler), but triggers when the command is embedded or double-invoked in-process.
Source
Thrown at compiler-cli/src/shell.rs:29
pub fn command(paths: &ProjectPaths) -> Result<(), Error> {
// Build project
let _ = crate::build::main(
paths,
Options {
root_target_support: TargetSupport::Enforced,
warnings_as_errors: false,
codegen: Codegen::All,
compile: Compile::All,
mode: Mode::Dev,
target: Some(Target::Erlang),
no_print_progress: false,
},
crate::build::download_dependencies(paths, crate::cli::Reporter::new())?,
)?;
// Don't exit on ctrl+c as it is used by child erlang shell
ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler");
// Prepare the Erlang shell command
let mut command = Command::new("erl");
// Print character lists as lists
let _ = command.arg("-stdlib").arg("shell_strings").arg("false");
// Specify locations of .beam files
let packages = paths.build_directory_for_target(Mode::Dev, Target::Erlang);
for entry in crate::fs::read_dir(packages)?.filter_map(Result::ok) {
let _ = command.arg("-pa").arg(entry.path().join("ebin"));
}
crate::cli::print_running("Erlang shell");
// Run the shell
tracing::info!("Running OS process {:?}", command);
let _ = command.status().map_err(|e| Error::ShellCommand {View on GitHub (pinned to 7e623aa83d)
Solutions
- Invoke `gleam shell` as a subprocess rather than linking the crate into a handler-owning host.
- Ensure only one component in the process manages ctrl-c; remove your handler before calling this code (not generally possible — hence prefer subprocess).
- Maintainer-level fix: replace expect with graceful handling of ctrlc::Error::MultipleHandlers, since an existing no-op-equivalent handler is harmless.
- Check the panic precedes any erl spawn — no Erlang node or build artifacts are affected; just retry in a clean process.
Example fix
// before: double in-process invocation
ctrlc::set_handler(move || {})?; // earlier in host
shell::command(&paths)?; // panics: handler already set
// after: one handler owner per process — spawn gleam instead
std::process::Command::new("gleam").arg("shell").status()?; Defensive patterns
Strategy: fallback
Try / catch
// Hosts that already own a signal handler: spawn `gleam shell` instead of
// calling the crate function — the child installs its own handler cleanly.
let status = std::process::Command::new("gleam")
.arg("shell")
.status()?
.code()
.unwrap_or(1);
// catch_unwind is a last resort for in-process use:
let _ = std::panic::catch_unwind(|| shell::command(&paths)); Prevention
- Run `gleam shell` as a child process in embedding scenarios; it needs its own process for signal handling anyway.
- Never call the shell command function twice in one process (integration tests included).
- Keep the panic benign: it fires before `erl` is spawned, so nothing to clean up — just retry in a fresh process.
When it happens
Trigger: A host process that already registered a ctrl-c handler then invokes the shell command in-process; calling the shell command function twice (tooling/tests); a sandboxed runtime where signal setup is denied.
Common situations: Embedded gleam tooling, integration tests that drive `gleam shell` logic directly, or wrappers that install their own interrupt handling for job control.
Related errors
- Error setting Ctrl-C handler
- `panic` expression evaluated.
- Writing warning to stderr
- Final result error writing
- joining_lsp_threads
AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17).
Data as JSON: /api/errors/a306d14ac26ee71e.
Report an issue: GitHub.