{"record":{"id":"d82983ed0d32037f","repo":"fish-shell/fish-shell","slug":"setpgid","errorCode":null,"errorMessage":"setpgid","messagePattern":"setpgid","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/reader/reader.rs","lineNumber":4963,"sourceCode":"        // just an \"invalid\" pid for all intents and purposes.\n        if owner == Ok(NullablePid::from_raw(0)) {\n            let _ = tcsetpgrp(STDIN_FD, shell_pgid);\n            // Since we expect the above to work, call `tcgetpgrp()` immediately to\n            // avoid a second pass through this loop.\n            owner = tcgetpgrp(STDIN_FD);\n        }\n        if owner == Err(Errno::ENOTTY) {\n            if !is_interactive_session() {\n                // It's OK if we're not able to take control of the terminal. We handle\n                // the fallout from this in a few other places.\n                break;\n            }\n            // No TTY, cannot be interactive?\n            flog!(\n                warning,\n                wgettext!(\"No TTY for interactive shell (tcgetpgrp failed)\")\n            );\n            perror(\"setpgid\");\n            exit_without_destructors(1);\n        }\n        if owner == Ok(shell_pgid) {\n            break; // success\n        } else {\n            if check_for_orphaned_process(loop_count, shell_pgid) {\n                // We're orphaned, so we just die. Another sad statistic.\n                let pid = getpid();\n                flog!(\n                    warning,\n                    sprintf!(\n                        \"I appear to be an orphaned process, so I am quitting politely. My pid is %d.\",\n                        pid.as_raw()\n                    )\n                );\n                exit_without_destructors(1);\n            }\n","sourceCodeStart":4945,"sourceCodeEnd":4981,"githubUrl":"https://github.com/fish-shell/fish-shell/blob/35fd72ad034e8e75451debe29535240bbd689980/src/reader/reader.rs#L4945-L4981","documentation":"At startup an interactive fish attempts to create/verify its own process group with setpgid(2) so it can own the terminal. If tcgetpgrp/setpgid fail such that fish cannot control the TTY, it logs the 'No TTY for interactive shell' warning, perror(\"setpgid\"), and exits via exit_without_destructors(1). This is a hard fatal condition — the interactive reader cannot run without a controlling terminal.","triggerScenarios":"Launching fish with -i (interactive) when stdin is not a tty or has no controlling terminal: e.g. `setsid fish`, running under a daemon/CI without a pty, or the shell's pgid differs from the tty's foreground pgid and setpgid fails with EPERM (session leader restrictions) in src/reader/reader.rs:4963.","commonSituations":"`ssh host fish -i` with stdin redirected from a file; running fish -i inside Docker without -t; job-control-less environments (cron, systemd services); EPERM because the process is a session leader that cannot change its pgid.","solutions":["Don't force -i when there is no tty; run fish non-interactively (remove -i/--interactive)","Allocate a pty: use `script -qc fish /dev/null`, `ssh -t`, or `docker run -it`","Avoid starting fish via setsid/daemonization if interactive use is intended","Check job control support: ensure the parent shell isn't disabling job control and the process isn't a session leader before setpgid"],"exampleFix":"// before (script/CI)\nfish -i -c 'my_interactive_setup'\n// after\nfish -c 'my_interactive_setup'   # drop -i; no tty available\n// or, to force a pty:\nscript -qc \"fish -i\" /dev/null","handlingStrategy":"validation","validationCode":"# Ensure a controlling tty exists before launching interactive fish:\nif not tty >/dev/null 2>&1\n    echo \"no controlling terminal; run fish without -i or allocate a pty (ssh -t, docker -it, script -qc)\" >&2\n    exit 1\nend","typeGuard":null,"tryCatchPattern":"// Only enter interactive mode when the tty is controllable:\nlet tty_ok = unsafe { libc::tcgetpgrp(libc::STDIN_FILENO) } != -1;\nif !tty_ok {\n    flog!(warning, \"No TTY for interactive shell\");\n    // fall back to non-interactive instead of exit_without_destructors(1)\n}","preventionTips":["Never pass -i/--interactive when stdin is a file, pipe, or CI runner","Use ssh -t / docker run -it / script -qc to guarantee a pty for interactive shells","Don't start interactive fish under setsid or session leaders that can't setpgid","Check `tty` succeeds in wrapper scripts before exec'ing fish -i"],"tags":["unix","tty","process-group","interactive","fatal"],"backgroundTag":"no-controlling-terminal","analyzedSha":"35fd72ad034e8e75451debe29535240bbd689980","analyzedAt":"2026-09-03T01:23:54.744Z","contentChangedAt":"2026-09-03T01:23:54.744Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}