{"record":{"id":"a306d14ac26ee71e","repo":"gleam-lang/gleam","slug":"error-setting-ctrl-c-handler-a306d1","errorCode":null,"errorMessage":"Error setting Ctrl-C handler","messagePattern":"Error setting Ctrl-C handler","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler-cli/src/shell.rs","lineNumber":29,"sourceCode":"\npub fn command(paths: &ProjectPaths) -> Result<(), Error> {\n    // Build project\n    let _ = crate::build::main(\n        paths,\n        Options {\n            root_target_support: TargetSupport::Enforced,\n            warnings_as_errors: false,\n            codegen: Codegen::All,\n            compile: Compile::All,\n            mode: Mode::Dev,\n            target: Some(Target::Erlang),\n            no_print_progress: false,\n        },\n        crate::build::download_dependencies(paths, crate::cli::Reporter::new())?,\n    )?;\n\n    // Don't exit on ctrl+c as it is used by child erlang shell\n    ctrlc::set_handler(move || {}).expect(\"Error setting Ctrl-C handler\");\n\n    // Prepare the Erlang shell command\n    let mut command = Command::new(\"erl\");\n\n    // Print character lists as lists\n    let _ = command.arg(\"-stdlib\").arg(\"shell_strings\").arg(\"false\");\n\n    // Specify locations of .beam files\n    let packages = paths.build_directory_for_target(Mode::Dev, Target::Erlang);\n    for entry in crate::fs::read_dir(packages)?.filter_map(Result::ok) {\n        let _ = command.arg(\"-pa\").arg(entry.path().join(\"ebin\"));\n    }\n\n    crate::cli::print_running(\"Erlang shell\");\n\n    // Run the shell\n    tracing::info!(\"Running OS process {:?}\", command);\n    let _ = command.status().map_err(|e| Error::ShellCommand {","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/gleam-lang/gleam/blob/7e623aa83da3776faee50ca4ab9a6c40124acd95/compiler-cli/src/shell.rs#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"Embedded gleam tooling, integration tests that drive `gleam shell` logic directly, or wrappers that install their own interrupt handling for job control.","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."],"exampleFix":"// before: double in-process invocation\nctrlc::set_handler(move || {})?;        // earlier in host\nshell::command(&paths)?;                 // panics: handler already set\n\n// after: one handler owner per process — spawn gleam instead\nstd::process::Command::new(\"gleam\").arg(\"shell\").status()?;","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Hosts that already own a signal handler: spawn `gleam shell` instead of\n// calling the crate function — the child installs its own handler cleanly.\nlet status = std::process::Command::new(\"gleam\")\n    .arg(\"shell\")\n    .status()?\n    .code()\n    .unwrap_or(1);\n// catch_unwind is a last resort for in-process use:\nlet _ = std::panic::catch_unwind(|| shell::command(&paths));","preventionTips":["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."],"tags":["panic","signal","ctrl-c","sigint","erlang-shell","gleam-shell","process"],"backgroundTag":"signal-handler-conflict","analyzedSha":"7e623aa83da3776faee50ca4ab9a6c40124acd95","analyzedAt":"2026-08-17T00:07:02.091Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}