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

  1. Invoke `gleam shell` as a subprocess rather than linking the crate into a handler-owning host.
  2. Ensure only one component in the process manages ctrl-c; remove your handler before calling this code (not generally possible — hence prefer subprocess).
  3. Maintainer-level fix: replace expect with graceful handling of ctrlc::Error::MultipleHandlers, since an existing no-op-equivalent handler is harmless.
  4. 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

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


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