Does Closing the Terminal Kill Your AI Coding Agent? How Session Hosts Stay Alive

Terminal closure kills AI agents via two-hop SIGHUP; only tmux-like solutions keep an interactive terminal.
The article analyzes why closing a terminal kills an AI coding agent: the kernel sends SIGHUP to the shell (session leader) when the pty master closes, and the shell forwards it to all jobs. nohup and disown keep processes alive but the terminal is hung up, blocking interaction; setsid detaches the session but loses the terminal; only tmux, with an independent server holding the pty, decouples the agent session from the user's window for re-attachment. Six design principles for agent hosts are distilled: separate session process ignoring SIGHUP, holding pty master, UI as socket client, output ring buffer, protocol version validation, and self-exit with no sessions. Windows has a gap because ConPTY can't move between processes; remote SSH + tmux remains reliable.
You hand a long refactoring task to an AI coding agent, watch it start running the test suite, and then casually close the terminal window—the work is in its hands, after all. But when you open a new window, the process is gone, along with all progress that never made it to disk.

This isn't the terminal application "hunting down" the agent. According to an analysis by developer xgabriel on his technical blog, the real culprit is a two-hop signal delivery between the kernel and the shell: the kernel tells the shell its terminal hung up, and the shell forwards SIGHUP to every job it manages. The usual lifesavers—nohup, disown, setsid, and tmux—each sever a different link in the chain, but only one lets you type into it again.
Four Process IDs That Decide Life or Death
To understand why the agent dies, you first need to know Unix's two-layer grouping mechanism. A process group (PGID) represents a "job"—all processes launched by a single command line, which Ctrl+C can hit all at once. A session (SID) is a group of process groups sharing a controlling terminal; the process that creates the session is the session leader—in a terminal window, that role belongs to your shell.
Open a new window and run two commands to see it clearly:
sleep 600 &
ps -o pid,ppid,pgid,sid,tpgid,tty,comm -p "$$,$!"
The four key columns are PID, PGID, SID, and TPGID. The shell's PID equals its SID because it leads the entire session; sleep has its own PGID because job control puts each job in its own group. Both rows share the same TTY (the slave side of the window's pty). TPGID is the terminal's foreground process group—when you're sitting at the prompt it's the shell's group; when the agent runs in the foreground it's the agent's group.
macOS's ps deliberately omits a column: Apple's tool has no sid keyword at all, and the closest, sess, prints a hexadecimal kernel session pointer rather than an ID. On a Mac, PGID, TPGID, and TTY are sufficient.
SIGHUP and job control: SIGHUP (hangup signal) originally got its name from serial line disconnection; today in pseudo-terminal scenarios it's triggered by the kernel when the master side closes. Its default action is to terminate the process, but it can be caught or ignored. Job control relies on process groups: the shell creates a separate PGID for each pipeline or command so it can send SIGINT (Ctrl+C), SIGTSTP (Ctrl+Z), etc. to the foreground group. When the session leader exits, POSIX requires it to send SIGHUP to the controlling terminal's foreground process group—this is the source of the second signal the agent might receive.
Orphaned process groups: If every member of a process group has a parent outside the group or that has exited, the group becomes orphaned. POSIX specifies: if an orphaned group has members in a stopped state, the system sends SIGHUP and SIGCONT to all members of the group to prevent stopped processes from lingering forever. This explains the trap in the article where Ctrl+Z followed by disown still results in death—the stopped agent makes the group orphaned, triggering the kernel signal.
What Happens the Moment You Close the Window
The terminal window holds the master side of a pseudo-terminal pair; the shell and everything it launches connect to the slave side. Closing the window means closing the master—either the application closes it actively or the application exits.
Linux's pty driver calls tty_vhangup on the slave when the master closes. This hangup operation walks all processes in the terminal session, sends SIGHUP and SIGCONT to the session leader (your shell), and clears the controlling terminal of all processes in the session that point to that terminal. Afterward, reads from that tty return 0 (EOF) and writes fail with EIO. POSIX specifies the same rules for any hung-up terminal.
At the moment of hangup, the kernel sends a signal to only one process: the shell. The second hop is the shell's own policy. Bash exits upon receiving SIGHUP, and before exiting it "resends SIGHUP to all running or stopped jobs." macOS's default login shell since 10.15, zsh, also has a HUP option that's on by default, sending SIGHUP to running jobs when the shell exits.
An agent running in the foreground is no different from any other job—it also gets SIGHUP from the shell. It might even get a second one: POSIX specifies that when the controlling process exits it sends SIGHUP to the terminal's current foreground group, and Linux remembers which group was in the foreground when the terminal hung up. So even if the shell forwards nothing, a foreground agent will still be hit by the signal. And even if it ignores both, the terminal beneath it still returns EOF on every read.
Five Ways to Sleep, One Closed Window
The original author designed a clever controlled experiment, giving each technique a different duration to distinguish the survivors:
sleep 601 & # ordinary background job
nohup sleep 602 & # ignore SIGHUP
sleep 603 & disown # remove from job table
setsid -f sleep 604 # Linux only
tmux new -d -s agent 'sleep 605'
After closing the window, check in a new window with ps -A -o pid,ppid,pgid,tty,command | grep '[s]leep 60'. The results are as follows:
sleep 601is the shell's job and is terminated by the SIGHUP the shell forwards.sleep 602gets the same SIGHUP but ignores it—nohupsets SIGHUP to ignored before running the command, and when stdout is a terminal it also appends output tonohup.out.sleep 603never receives a signal.disownremoves the job from the shell's job table; the shell has nothing to forward, and the kernel only notified the session leader.sleep 604lives in a separate session with no controlling terminal; the hangup traversal covers a session it never joined.sleep 605belongs to the tmux server.
There's a pitfall worth heeding: if a process group becomes orphaned while a member is in a stopped state, it receives SIGHUP plus SIGCONT. Press Ctrl+Z on the agent, disown it, and close the window—it still dies.
Surviving Isn't the Same as Being Usable
Four sleeps survived, but for sleep the story ends there. An agent is different—it needs to draw a UI, read keypresses, and pause to ask you before executing commands. After nohup or disown, the terminal beneath it has hung up: every read is EOF, every write is EIO. After setsid, the terminal doesn't exist at all. The process may still be alive, but there's no channel to show its next question or bring back your answer.
tmux is the only solution in the experiment that preserves a usable terminal, because the agent never treated your window as its own terminal. When tmux starts its server it calls daemon(1, 0), detaching itself from the controlling terminal and pointing stdin/stdout/stderr at /dev/null. Each pane is a separate pseudo-terminal that the server itself opens and reads. The tmux you type is just a client; the server and clients communicate through a socket under /tmp.
Closing the window hangs up the window's pty, and the tmux client attached to that window falls with the shell. But on the other side of the socket, the server still holds the pane, and the agent is still running inside it. From a new window, run tmux attach -t agent and the new client connects to the same pane. The hangup never touched the agent's terminal, because nothing in the agent's session is attached to your window.
Controlling terminal and pty semantics: The controlling terminal is the terminal device associated with a process's session; the foreground process group can be set via tcsetpgrp. A pseudo-terminal pair consists of a master and a slave; the slave emulates a traditional terminal. When the master closes, the kernel hangs up the slave: reads return 0 (EOF), writes return EIO, and the controlling terminal is cleared for all processes in the session. Even if a process ignores SIGHUP, subsequent I/O will fail. Therefore, a surviving process that needs interaction must have a non-hung-up terminal (like a tmux pane's pty), or it can only run as a terminal-less background task.
What Every Agent Host Should Have
Breaking tmux down to its minimum, any application that wants an agent to outlive its window needs this checklist:
- A separate long-lived process in its own session that ignores SIGHUP, so a dying UI or terminal can't take it down.
- That process holds each agent's pty master, with each agent running in its own session; the pty is its controlling terminal.
- The UI is just a client, connecting through a socket that only the user can open; when the UI disappears the session keeps running, unattached.
- Output arriving with no one attached goes into a buffer; a returning client can replay what it missed and ask the program to redraw.
- Replacing the UI binary doesn't affect a running host; both sides verify protocol version before communicating.
- The host exits on its own when there are no sessions left, not lingering as a stray daemon.
The original author illustrates this design with his own Hermes IDE: hermes-pty-host is a small background process that "owns the PTY and its program"; the application starts it when the first session needs it, from a versioned copy, so updates "won't touch a running host"; the application attaches through a user-specific Unix socket with a token, treats hosts with mismatched protocol versions as unavailable, and on return "re-attaches to sessions the host still holds, replays missed output, and prompts the program to redraw." Its main.rs calls setsid() under the comment "its own session: the application's terminal or death must never take the host down," server.rs ignores SIGHUP at startup, and by default keeps a 2 MiB output ring buffer per session. The pty comes from wezterm's portable-pty, which calls setsid() before each spawned program's exec and can make the pty the controlling terminal.
Windows is an obvious gap. ConPTY "can't be moved between processes," so on Windows the host reports itself unsupported and sessions stay within the application. On Windows, or when an agent needs to outlive a laptop, tmux over SSH on a remote machine remains the right answer.
daemon(1,0) and setsid: daemon(nochdir, noclose) is a libc function that internally calls fork, setsid, and optionally redirects standard streams to /dev/null. setsid() creates a new session, making the calling process the session leader with no controlling terminal. The tmux server uses this to detach from the original terminal and become an independent session leader, thus unaffected by window closure. An agent host likewise needs to call setsid() and ignore SIGHUP to withstand signals when the parent process exits.
Unix domain sockets and permissions: Unix domain sockets (AF_UNIX) are used for inter-process communication on the same host, supporting stream and datagram. Sockets on filesystem paths are protected by file permissions; a host can create a user-only-readable/writable socket (e.g., chmod 0600) to prevent other users from connecting. Clients attach via connect(), servers accept via accept(), decoupling UI from host.
Editorial Perspective
The value of this article lies in reducing a daily pain point to a clear chain of system calls: kernel → session leader → job table, each hop with its own way of being cut. It also debunks a common misconception—process survival isn't usability; an agent without a usable terminal is just a zombie occupying memory.
For teams building AI coding tools, this host design checklist is almost usable directly as a requirements document. Worth adding: the ring buffer size, protocol version strategy, and no-session exit timeout are the real knobs that determine experience quality, not the surface-level choice of "whether to use tmux."
One-line Summary
Terminal closure kills AI agents via SIGHUP through two hops—kernel and shell—and only tmux-like solutions preserve an interactive terminal.
Paragraph Summary
The article dissects the full chain by which closing a terminal kills an AI coding agent process: the kernel sends SIGHUP to the session leader (the shell) when the pty master closes, and the shell forwards it to all jobs. nohup and disown keep the process alive but the terminal is hung up, so the agent can't interact; setsid detaches the process from the session but loses the terminal entirely; only tmux, through an independent server holding the pty, decouples the agent session from the user's window, enabling re-attachment. The author distills six design principles for agent hosts: a separate session process ignoring SIGHUP, holding the pty master, UI as socket client, output ring buffer, protocol version validation, and self-exit with no sessions. Windows has an architectural gap because ConPTY can't move between processes; remote SSH + tmux remains a reliable solution.
Related articles

Huberman's Rational Supplement Guide: A Five-Layer Health Framework and Scientific Decision-Making
Stanford neurobiology professor Huberman proposes a rational supplement framework: behavior and nutrition are the foundation, basic combination supplements serve as insurance, and sleep/hormonal/cognitive directions should stick to single-ingredient experiments. Omega-3 is the top single-ingredient choice, and melatonin is not recommended.

The Unsung Painter Behind Gilded Age Manhattan Interior Design
HN discussion highlights the painter behind Gilded Age Manhattan interiors: he created murals and decorative paintings but was long overlooked as his work was seen as architectural appendage.

Can AI Agents Be Socially Engineered? Understanding Prompt Injection and Tool Permission Risks
Through Invariant Lab's GitHub Issue injection and Brave's AI browser attack, this article analyzes how Prompt Injection induces AI Agents to perform unauthorized actions, and offers protection advice across three layers: least privilege, tool validation, and operation auditing.