# reptyr: move a running process onto the terminal you are sitting at

> reptyr attaches an already-running program to your current terminal, including its input, its window size and Ctrl-C, by changing the process controlling terminal through ptrace. It works on Linux and FreeBSD on i386, x86_64 and ARM, and it needs ptrace_scope turned down on distributions that ship Yama.

**nelhage/reptyr** — Reparent a running program to a new terminal

- Repository: https://github.com/nelhage/reptyr
- Stars: 6,355 · Forks: 231
- Language: C
- License: MIT
- Published: 2026-10-13 · Updated: 2026-10-13 · Language: en
- Canonical page: https://hysenlabs.com/projects/nelhage-reptyr

## What reparenting a process actually buys you

reptyr takes a PID and attaches that process to your current terminal. After it runs, the program takes input from and writes output to the new terminal, and the README is specific that this includes Ctrl-C and Ctrl-Z.

The scenario that motivates it is narrow and real. You start a long-running process over ssh, you have to leave, and you do not want to interrupt it. The instruction in the README is to start a terminal multiplexer, use reptyr to grab the process, then kill the ssh session and go home.

What makes this different from a multiplexer alone is that the process was not started inside the multiplexer. A tmux or screen session owns the processes launched within it; reptyr adopts one that was launched elsewhere, which is the case that actually happens to people who started work before they thought about leaving.

There is one documented behaviour that does not fully work. If you background the attached process, you will still have to run bg or fg in the old terminal, and the README says this would require patching your shell. So the process comes across, and then one job-control operation does not.

The tool is small: a single binary, a man page, and a README written in the first person by its author.

## The documented sequence, start to finish

The README gives a concrete pattern with top as the example, and it is worth following in order rather than improvising, because each step exists to solve a specific problem.

Start the long-running process, stop it with Ctrl-Z, and resume it in the background with bg. Then find its PID, and note that the -l flag on jobs is what makes the number appear:

```bash
jobs -l
```

which gives a line like `[1]+  4711 Stopped (signal)        top`. Disown the job from its current parent with disown top, after which jobs will no longer list it but ps -a will. That step is what removes it from the shell's job table so it can be adopted cleanly.

Next, start your multiplexer of choice, tmux in the example, and attach the process to the terminal you are now sitting in:

```bash
reptyr 4711
```

Finally detach the multiplexer with Ctrl-A D and close the ssh session. When you reconnect and run tmux attach, the process is there, still running, attached to a terminal you own.

The disown step is the part people skip, and it is the one that makes the difference between reparenting a job and fighting with a shell that still thinks it owns the process.

## Why a shell script called screenify is not the same

One section of the README answers a specific question: isn't this just screenify? The comparison is the most useful part of the document, because it names three concrete failures.

A shell script called screenify has circulated for nearly ten years and uses gdb to approach the same goal. The author lists three ways it falls short. Attach a less with it and input still arrives from the old terminal. Attach an ncurses program and resize the window, and the application never hears about it, so no SIGWINCH reaches it and no redraw happens. And Ctrl-C typed at the new terminal does nothing.

Those three failures share one root cause, and it is the same thing reptyr claims as its differentiator: it actually changes the controlling terminal of the process it is attaching. Input routing, resize notification and signal delivery all follow from which tty the kernel thinks the process belongs to, so a tool that leaves that unchanged cannot fix any of them.

The phrasing is the author making a claim about his own tool: he says reptyr fixes all of these problems and is the only such tool he knows of that does so.

A blog post referenced from the README explains the controlling terminal mechanics in more detail, for the case where you need to reason about why it worked.

## The mechanism: ptrace, syscall level, and the controlling terminal

reptyr uses the ptrace system call to attach to the target and control it at the syscall level. The README is direct about the consequence: the tool is highly dependent on the details of the syscall API, the available syscalls, and terminal ioctls.

That dependency explains the platform story. A port to another operating system is described as technically feasible but requiring significant low-level knowledge of the relevant platform, and it may entail significant refactors. The same section says ports to other architectures should be straightforward, and in most cases as simple as adding an arch/ARCH.h file and adding a clause to the ifdef ladder in ptrace.c.

So the difficulty is the kernel interface, not the CPU. Architectures are cheap because the syscall surface is shared; operating systems are expensive because the surface differs.

The reptyr -l flag is a smaller feature with an interesting use. It creates a new pseudo-terminal pair with nothing attached to the slave end and prints the name. If you are debugging a program in gdb you can pass that name to set inferior-pty, and because no existing program is listening on that tty it works much better than handing gdb an existing shell's terminal. In other words, the same primitive that makes reptyr work also gives you a clean pty for a debugger.

The project also includes reallocarray.c and reallocarray.h in the tree, which is a portability shim for a memory allocation function that older systems lack.

## ptrace_scope: the setting that decides whether it works

Ubuntu Maverick and later ship with attaching to another process disabled for security reasons, and reptyr needs that call. Both the temporary and the permanent fixes are given.

Temporarily, as root:

```bash
echo 0 > /proc/sys/kernel/yama/ptrace_scope
```

Permanently, by editing /etc/sysctl.d/10-ptrace.conf, which the README notes also contains more information about exactly what the setting accomplishes.

That file is worth opening before you edit it. The setting is part of Yama, a Linux security module, and loosening it weakens a protection that exists specifically to stop one process from inspecting another. In a single-user development machine the trade is usually obvious; on a shared host or a build server it is not.

The consequence of the trade is worth stating plainly. A tool whose entire purpose is to attach to a running process and take over its terminal is, by design, a tool that can do that to processes you did not start in that terminal. Combined with ptrace_scope set to zero, that is a meaningful capability on a machine with other users or other services.

If you are only debugging, reptyr -l gives you a spare pty without loosening anything, and gdb can drive it.

## Building it and what the tree tells you

The build is a plain Makefile with no dependency on a package ecosystem, which is appropriate for a tool of this age and size. CFLAGS are overridden to add -MD, -Wall, -Werror and -D_GNU_SOURCE, so warnings are errors and GNU extensions are available.

Platform support is expressed as object files chosen by uname. Linux adds platform/linux/linux_ptrace.o and platform/linux/linux.o. FreeBSD adds the freebsd equivalents and appends -lprocstat to LDFLAGS, which is the FreeBSD process statistics library. That single conditional is the clearest statement of how differently the two platforms present process information.

Installation is careful about what it puts on the system. The install target installs the binary with mode 755, the English man page reptyr.1, a French man page reptyr.fr.1 under fr/man1, and a bash completion file, each into DESTDIR-aware directories derived from PREFIX, which defaults to /usr/local and can be overridden from the command line, for example make PREFIX=/usr.

Tests are unusual in a C project of this vintage. The Makefile builds a separate test/victim binary and then runs two Python scripts, test/basic.py and test/tty-steal.py, through a variable defaulting to python2, and a DISABLE_TESTS value can skip straight to the build. A Vagrantfile sits at the top level for provisioning a test machine.

Documentation is unusually thorough for the scope: reptyr.1 and reptyr.fr.1 are real manual pages, reptyr.bash provides completions, and the README ends with a credits section, a URL and a pronunciation note, where the author says he says it like repeater but accepts re-P-T-Y-er.

## Where reptyr is the wrong tool

Four boundaries are documented or implied, and they are mostly about scope rather than bugs.

The backgrounding caveat is the clearest. Once you attach a process and then background it, you still need bg or fg in the old terminal, and the README says that fixing it in a reasonable way needs a patch to your shell. Job control across a terminal move is the hard case, and this tool handles the common case rather than the complete one.

FreeBSD is supported but not complete. The README states that not all functionality is currently available there and names reptyr -T as unsupported, so a feature you rely on on Linux may not exist on FreeBSD.

Architecture coverage is i386, x86_64 and ARM. Anything else needs a new arch header and a clause in the ptrace ifdef ladder, which is cheap to do and not free either.

And the security model is a design choice, not a defect. Attaching to arbitrary processes requires ptrace access and lets a process take over a terminal, which is the same primitive a debugger uses. On a multi-user machine, loosen ptrace_scope only if you have decided that is acceptable.

What reptyr is not is a session manager. If your work is inside tmux or screen from the start, you have no orphan process to adopt and no reason to run this.

## Conclusion

Adopt reptyr when a long job started over ssh must survive your disconnect, which is the exact scenario it was written for: background the process, disown it, start a multiplexer, reptyr the PID, and close the ssh session knowing the work continues. Do not adopt it expecting to move an arbitrary process between terminals without preparing it, because the documented sequence requires the job to be stopped, resumed, disowned and identified with jobs -l first. Verify four things. Check ptrace_scope before anything else, since Ubuntu Maverick and later disable attaching by default and the setting is /proc/sys/kernel/yama/ptrace_scope, fixable temporarily or through /etc/sysctl.d/10-ptrace.conf. Confirm your platform, because Linux and FreeBSD are supported with not all functionality on FreeBSD, including no support for reptyr -T, and only i386, x86_64 and ARM are covered. Read the caveat about backgrounded processes, since a job you background after attaching still needs bg or fg in the old terminal, and the README calls this out as the case that cannot be fixed without patching your shell. And decide whether the security model suits you, since reptyr can steal a terminal from another process, which is a capability rather than a bug. Start with reptyr -l to get a free pty before you touch anything live.

## FAQ

### How do I use reptyr to move a running process to another terminal?

Stop the process with Ctrl-Z, resume it with bg, find its PID with jobs -l, disown it, start tmux or another multiplexer, and run reptyr with that PID. Detach the multiplexer and you can close the ssh session.

### Why does reptyr fail with a permission error on Ubuntu?

Ubuntu Maverick and later disable attaching to other processes by default. The README gives the temporary fix of echoing 0 into /proc/sys/kernel/yama/ptrace_scope as root, or editing /etc/sysctl.d/10-ptrace.conf permanently.

### What platforms does reptyr support?

Linux and FreeBSD, with not all functionality available on FreeBSD, including no support for reptyr -T. Architectures covered are i386, x86_64 and ARM, and other ports need an arch header plus a clause in the ptrace ifdef ladder.

### What does reptyr -l do?

It creates a new pseudo-terminal pair with nothing attached to the slave end and prints the name. You can pass that name to set inferior-pty when debugging a program in gdb, which works better than handing gdb an existing shell's terminal.

### How does reptyr differ from the screenify script?

The README lists three screenify failures: less still reads from the old terminal, ncurses programs do not notice a resize, and Ctrl-C does not work. reptyr changes the controlling terminal of the attached process, which is what fixes all three.

## Sources

- [Issues](https://github.com/nelhage/reptyr/issues)
- [License: MIT](https://github.com/nelhage/reptyr/blob/master/LICENSE)
- [nelhage/reptyr on GitHub](https://github.com/nelhage/reptyr)
- [README](https://github.com/nelhage/reptyr/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nelhage-reptyr
