Skip to content

Commit 5d6e62e

Browse files
JAORMXclaude
andcommitted
Fix devpts mount to enable PTY allocation for unprivileged users
Add newinstance,ptmxmode=0666,mode=0620,gid=5 options to the devpts mount and create a /dev/ptmx -> /dev/pts/ptmx symlink. Without these, TUI applications (e.g. Claude Code) hang inside the VM because unprivileged users cannot allocate PTYs. Fixes #37 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c38d472 commit 5d6e62e

2 files changed

Lines changed: 12 additions & 2 deletions

File tree

guest/mount/mount.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func Essential(logger *slog.Logger) error {
2828
{"proc", "/proc", "proc", syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC, ""},
2929
{"sysfs", "/sys", "sysfs", syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC, ""},
3030
{"devtmpfs", "/dev", "devtmpfs", syscall.MS_NOSUID | syscall.MS_NOEXEC, ""},
31-
{"devpts", "/dev/pts", "devpts", syscall.MS_NOSUID | syscall.MS_NOEXEC, ""},
31+
{"devpts", "/dev/pts", "devpts", syscall.MS_NOSUID | syscall.MS_NOEXEC, "newinstance,ptmxmode=0666,mode=0620,gid=5"},
3232
{"tmpfs", "/tmp", "tmpfs", syscall.MS_NOSUID | syscall.MS_NODEV, "size=256m"},
3333
{"tmpfs", "/run", "tmpfs", syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC, "size=64m"},
3434
}
@@ -47,6 +47,16 @@ func Essential(logger *slog.Logger) error {
4747
}
4848
}
4949

50+
// Replace /dev/ptmx with a symlink to /dev/pts/ptmx. With the
51+
// newinstance devpts mount, the PTY multiplexer lives at
52+
// /dev/pts/ptmx, but glibc's posix_openpt() opens /dev/ptmx.
53+
// devtmpfs may auto-create /dev/ptmx as a device node, so remove
54+
// it first to ensure the symlink succeeds.
55+
_ = os.Remove("/dev/ptmx")
56+
if err := os.Symlink("/dev/pts/ptmx", "/dev/ptmx"); err != nil {
57+
return fmt.Errorf("creating /dev/ptmx symlink: %w", err)
58+
}
59+
5060
// Create standard /dev symlinks.
5161
symlinks := [][2]string{
5262
{"/proc/self/fd", "/dev/fd"},

guest/mount/mount_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestEssentialMountPoints(t *testing.T) {
4040
{"proc", "/proc", "proc", 0, ""},
4141
{"sysfs", "/sys", "sysfs", 0, ""},
4242
{"devtmpfs", "/dev", "devtmpfs", 0, ""},
43-
{"devpts", "/dev/pts", "devpts", 0, ""},
43+
{"devpts", "/dev/pts", "devpts", 0, "newinstance,ptmxmode=0666,mode=0620,gid=5"},
4444
{"tmpfs", "/tmp", "tmpfs", 0, "size=256m"},
4545
{"tmpfs", "/run", "tmpfs", 0, "size=64m"},
4646
}

0 commit comments

Comments
 (0)