Durable PTY
Terminals that don't die with your app.
Durable PTY keeps shells and AI coding agents running through app crashes and daemon restarts, and brings them back after a reboot.
Install with
Then run dpty install to start it at login. macOS (Apple silicon and Intel) and Linux (x86_64, aarch64). MIT licensed. Full install guide →
- One binary: daemon, CLI and MCP server
- Tested against the real claude and codex CLIs
- Every session in its own helper process
- macOS and Linux
Durability
What happens when things crash
Sessions live in the daemon, not in your app. Here is exactly what survives, honestly, including the one thing it does not yet do.
| Event | What happens |
|---|---|
| Your app / browser crashes | Nothing happens to the session. Reattach by id, name or label and get the current screen, then live output. |
| Daemon crashes or restarts | Each session's shim keeps the process running on its own. The daemon reattaches on start and catches up on missed output — nothing lost. If a shim is also gone, that session is marked lost and relaunched per its restart policy (default for agents: on-failure), same id, history kept. |
| Machine reboots or you log out | The processes themselves die — nothing can stop that. Sessions and their history survive in SQLite and are restored the next time the daemon starts. |
| A session keeps crash-looping | Capped at 5 automatic restarts per 60 seconds, then dpty gives up and records the session as restart-loop instead of retrying forever. |
Each session has its own small helper process — a shim — that owns the PTY and the running process, so the process itself keeps running through a daemon restart; see architecture. A machine reboot or logout still ends it; nothing can prevent that.
Command line
The CLI, as it actually runs
Real transcripts, not screenshots. The first command starts the daemon if it is not already running.
Agent sessions
claude and codex resume where they left off
dpty treats agent CLIs as a special case: it keeps the session id and resumes the agent with that id instead of just replaying a shell.
Finding the session id
claude is launched with --session-id <uuid>, so dpty knows the id from the moment the session starts. codex does not take an id up front, so dpty finds it by scanning ~/.codex/sessions after launch.
Other agents can be supported by passing --kind and --resume-cmd "grok --resume {id}", or by having the agent report its own id with a hook: dpty tag-agent "$DPTY_SESSION" <id>.
Resuming after a crash
On recovery dpty relaunches with the same cwd and environment and runs claude --resume <id> or codex resume <id>. The original prompt is dropped — the agent already has it in its own session — and every other flag you launched with is kept.
Integration points
Three ways in: socket, HTTP, MCP
The CLI, the HTTP API and the MCP server all call the same underlying RPC methods, so nothing behaves differently depending on how you connect.
Unix socket
~/.durable-pty/dpty.sock (mode 0600): JSON-line RPC, with an attach call that upgrades the connection to binary terminal frames.
HTTP and WebSocket
Default 127.0.0.1:7878, bearer token from dpty token (disable with --no-http): POST /rpc, GET /ws for live attach, GET /health.
MCP
Stdio, streamable HTTP at POST /mcp, and legacy SSE at GET /sse + POST /messages. Tools exposed:
pty_listlist every session and its statepty_newstart a new sessionpty_sendtype into a sessionpty_screenread the current screenpty_logsread raw outputpty_waitblock until a pattern appearspty_historyread typed commands and eventspty_restartrestart a sessionpty_killstop a session
Point an MCP client at it with:
"mcpServers": {
"dpty": {
"command": "dpty",
"args": ["mcp"]
}
}
}
Design
Architecture
Clients never hold the terminal — the daemon does, and it is the only thing that writes to SQLite. A per-session shim keeps a running process alive even while the daemon is restarted.
The shim keeps the real process running while the daemon is replaced or restarted, and the daemon re-attaches to it afterward and catches up on whatever output it missed. dpty stop leaves every session's shim running; dpty stop --sessions stops them too. More detail: how it works →.
Scope
What dpty is not
Said plainly, so you do not find out the hard way.
Not for Windows
macOS and Linux only. There is no Windows support today.
A reboot ends running processes
There is no way around this — the OS kills them. What dpty guarantees is that the session, its scrollback and its history survive and are restored on the next daemon start.
History and resume remain
Scrollback, typed commands and events are in SQLite before anything crashes, so you never lose the record even if the process itself is gone.
Agent resume is best-effort
claude and codex are resumed by session id with the original prompt dropped. Other agents need --resume-cmd or a reporting hook to work the same way.