| name | holdpty |
| description | Detached PTY sessions — launch commands in a real pseudo-terminal, attach/view/record later. Use when: launching agents or processes that need a real TTY but should run detached, supervising running agents, recording terminal sessions, viewing agent output. Triggers: detached terminal, background agent, attach to session, view terminal, record session, holdpty. |
holdpty — Detached PTY Sessions
Launch commands in a real pseudo-terminal. Attach, view, or dump output later.
Cross-platform: Windows (ConPTY, named pipes) + Linux (forkpty, UDS) + macOS.
Project Location
C:\dev\holdpty
Install & Build
cd C:\dev\holdpty
npm install
npm run build
Core Commands
Launch a session
--fg or --bg is required (no default).
holdpty launch --bg --name worker1 -- pi -p "analyze codebase"
holdpty launch --fg --name build -- make all
SESSION=$(holdpty launch --bg -- node server.js)
holdpty launch --bg --cols 200 --rows 50 -- /bin/zsh
-- is optional. PowerShell strips -- before it reaches the process, so holdpty launch --bg node server.js works the same as holdpty launch --bg -- node server.js.
Windows .cmd wrappers do NOT work. npm-installed CLIs on Windows use .cmd shims (e.g. pi.cmd, tsc.cmd). holdpty (via node-pty) cannot execute these — the holder will fail to start. Always resolve to the actual .js entry point:
holdpty launch --bg --name agent -- pi -p "prompt"
holdpty launch --bg --name agent -- node "C:\path\to\cli.js" -p "prompt"
cat "$(which pi)" | head -5
List sessions
holdpty ls
holdpty ls --json
Stale sessions (crashed holders) are auto-detected and cleaned on every ls.
Attach (interactive, single-writer)
holdpty attach worker1
Only one attachment at a time. Error if already attached. Requires a real TTY.
View (read-only, multiple viewers)
holdpty view worker1
Outputs real PTY data (escape sequences, TUI, colors) to stdout. Includes both buffer replay (history) and live stream. Multiple viewers allowed simultaneously.
Logs (dump buffer, exit — or follow live)
holdpty logs worker1
holdpty logs worker1 --tail 50
holdpty logs worker1 -f
holdpty logs worker1 -f --tail 50
holdpty logs worker1 -f --no-replay
Send (non-exclusive input injection)
holdpty send worker1 "npm test"
echo "exit" | holdpty send worker1 --stdin
Multiple senders can run concurrently — no exclusive lock. Works alongside attached clients and viewers. No ordering guarantee between concurrent senders.
Stop a session
holdpty stop worker1
Kills both the child process and the holder process.
Info
holdpty info worker1
Stdout Discipline
This matters for scripting and agent use:
| Command | stdout | stderr |
|---|
send | Nothing | Errors only |
launch --bg | Session name only | Nothing |
launch --fg | Session name | Nothing |
view | PTY data only | Status messages |
logs | PTY data only | Status messages |
ls | Session list | "No active sessions" if empty |
ls --json | JSON array | Nothing |
info | JSON object | Errors only |
attach | Terminal takeover | N/A |
stop | Nothing | Confirmation |
Exit Codes
| Command | Exit code |
|---|
launch --bg | 0 on success |
launch --fg | Child's exit code |
attach | Child's exit code (if child exits) or 0 (on detach) |
logs | 0 |
view | 0 |
send | 0 if sent; 1 if session not found or dead |
stop | 0 |
| Any error | 1 |
Environment Variables
| Variable | Purpose | Default |
|---|
HOLDPTY_DIR | Session metadata directory | %TEMP%\dt\ (Win) / $XDG_RUNTIME_DIR/dt/ (Linux) |
HOLDPTY_DETACH | Custom detach sequence (hex) | 0x01,0x64 (Ctrl+A then d) |
HOLDPTY_LINGER_MS | Shutdown linger time in ms | 5000 |
Platform Details
| Windows | Linux/macOS |
|---|
| PTY backend | ConPTY | forkpty |
| IPC transport | Named pipes (//./pipe/holdpty-<hash>-<name>) | Unix domain sockets ({dir}/{name}.sock) |
| Signal behavior | SIGTERM = instant TerminateProcess | SIGTERM = graceful |
| Metadata | %TEMP%\dt\{name}.json | $XDG_RUNTIME_DIR/dt/{name}.json |
Named pipes include a hash of HOLDPTY_DIR to isolate different environments.
Common Agent Patterns
Launch an agent and check on it later
SESSION=$(holdpty launch --bg --name analysis -- node.exe agent.js)
holdpty logs "$SESSION" | tail -20
Supervise a running agent
holdpty view worker1
Check if a session is alive
holdpty info worker1
holdpty ls --json
Scripting pattern
NAME=$(holdpty launch --bg --name myagent -- node.exe server.js)
echo "Launched: $NAME"
sleep 2
holdpty logs "$NAME" | grep "listening"
holdpty stop "$NAME"
Architecture (for developers)
- One holder process per session (no central daemon)
- Binary length-prefixed protocol:
[1B type][4B len BE][payload] (8 message types)
- 1MB ring buffer for output replay
- Filesystem is the registry (
.json metadata files)
- Raw byte relay (no terminal state machine / ANSI parsing)
Source: src/ — cli.ts, holder.ts, client.ts, protocol.ts, ring-buffer.ts, session.ts, platform.ts
What holdpty is NOT
- Not a process manager → use pm2 / systemd / nohup for lifecycle
- Not a terminal emulator → your terminal renders the output
- Not tmux/screen → no splits, tabs, config files, status bars