{"record":{"id":"2fd52f61edb6220d","repo":"Schniz/fnm","slug":"failed-to-grab-exit-code","errorCode":null,"errorMessage":"Failed to grab exit code","messagePattern":"Failed to grab exit code","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/commands/exec.rs","lineNumber":108,"sourceCode":"\n        log::debug!(\n            \"Running {binary} with PATH={path_env}\",\n            path_env = path_env.display()\n        );\n\n        let exit_status = Command::new(binary)\n            .args(arguments)\n            .stdin(Stdio::inherit())\n            .stdout(Stdio::inherit())\n            .stderr(Stdio::inherit())\n            .env(\"PATH\", path_env)\n            .spawn()\n            .map_err(|source| Error::CantSpawnProgram {\n                source,\n                binary: binary.clone(),\n            })?\n            .wait()\n            .expect(\"Failed to grab exit code\");\n\n        let code = exit_status.code().ok_or(Error::CantReadProcessExitCode)?;\n        std::process::exit(code);\n    }\n}\n\n#[derive(Debug, Error)]\npub enum Error {\n    #[error(\"Can't spawn program: {source}\\nMaybe the program {} does not exist on not available in PATH?\", binary.bold())]\n    CantSpawnProgram {\n        source: std::io::Error,\n        binary: String,\n    },\n    #[error(\"Can't read path environment variable\")]\n    CantReadPathVariable,\n    #[error(\"Can't add path to environment variable: {}\", source)]\n    CantAddPathToEnvironment { source: std::env::JoinPathsError },\n    #[error(\"Can't find version in dotfiles. Please provide a version manually to the command.\")]","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/Schniz/fnm/blob/86adc9676ceb2a509b21e75e74048b93c89f097d/src/commands/exec.rs#L90-L126","documentation":"`fnm exec --using <ver> <cmd...>` spawns the resolved binary with inherited stdio and calls `.wait()` on the child to obtain its ExitStatus. If the OS fails to reap the child (an io::Error such as ECHILD — the pid was already reaped elsewhere), `.expect(\"Failed to grab exit code\")` panics instead of returning. This is distinct from `Error::CantReadProcessExitCode`, which is the graceful error returned when the child was killed by a Unix signal (`exit_status.code()` is None).","triggerScenarios":"`fnm exec --using <ver> <cmd>` where wait() itself errors: the child daemonizes/forks and its pid gets reaped by another party, a supervisor/tracer (strace, valgrind, procdump) interferes with child reaping, or the process is killed externally in a way that races the wait call.","commonSituations":"Wrapping self-daemonizing tools (daemons, `npm run` scripts that spawn and detach, Windows GUI tools) with `fnm exec`; running under sandboxing or observability layers that reap children; PID-namespace/container edge cases; OOM-killer racing the wait.","solutions":["Retry the command once — a reaping race is often transient.","Don't wrap detached/daemonizing programs with `fnm exec`; apply the env first (`eval \"$(fnm env)\"; mytool`) and run the tool directly.","Check kernel logs (`dmesg`) for OOM kills if children vanish.","If patching fnm: replace `.expect(...)` with `.map_err(...)?` and propagate a graceful error mirroring CantSpawnProgram."],"exampleFix":"// before (src/commands/exec.rs)\n.wait()\n.expect(\"Failed to grab exit code\");\n\n// after\nlet exit_status = child\n    .wait()\n    .map_err(|source| Error::CantWaitForProcess { source })?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let exit_status = Command::new(binary).args(args).spawn()\n    .map_err(|source| Error::CantSpawnProgram { source, binary })?\n    .wait();\nmatch exit_status {\n    Ok(status) => {\n        let code = status.code()\n            .ok_or(Error::CantReadProcessExitCode)?; // signal-killed child\n        std::process::exit(code);\n    }\n    Err(e) if e.kind() == std::io::ErrorKind::Interrupted => /* retry wait once */,\n    Err(e) => return Err(anyhow::anyhow!(\"failed to reap child process: {e}\")),\n}","preventionTips":["Don't wrap self-daemonizing programs with `fnm exec`; export the env and run them directly.","In wrapper scripts, treat a non-zero/absent fnm exit as 'run without fnm' rather than crashing.","Keep tracing/supervision layers from reaping fnm's children."],"tags":["rust","process","exit-code","exec","unix-signals","panic"],"backgroundTag":"process-wait-failed","analyzedSha":"86adc9676ceb2a509b21e75e74048b93c89f097d","analyzedAt":"2026-08-16T21:43:05.725Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}