{"record":{"id":"e52c086a27c8388a","repo":"hashicorp/nomad","slug":"failed-to-open-a-tty-v","errorCode":null,"errorMessage":"failed to open a tty: %v","messagePattern":"failed to open a tty: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/shared/executor/exec_utils.go","lineNumber":51,"sourceCode":"\n\t// processStart starts the process, like `exec.Cmd.Start()`\n\tprocessStart func() error\n\n\t// processWait blocks until command terminates and returns its final state\n\tprocessWait func() (*os.ProcessState, error)\n}\n\nfunc (e *execHelper) run(ctx context.Context, tty bool, stream drivers.ExecTaskStream) error {\n\tif tty {\n\t\treturn e.runTTY(ctx, stream)\n\t}\n\treturn e.runNoTTY(ctx, stream)\n}\n\nfunc (e *execHelper) runTTY(ctx context.Context, stream drivers.ExecTaskStream) error {\n\tptyF, tty, err := e.newTerminal()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open a tty: %v\", err)\n\t}\n\tdefer tty.Close()\n\n\tif err := e.setTTY(tty); err != nil {\n\t\treturn fmt.Errorf(\"failed to set command tty: %v\", err)\n\t}\n\tif err := e.processStart(); err != nil {\n\t\treturn fmt.Errorf(\"failed to start command: %v\", err)\n\t}\n\n\tvar wg sync.WaitGroup\n\terrCh := make(chan error, 3)\n\n\tpty, err := ptyF()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get pty: %v\", err)\n\t}\n","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/drivers/shared/executor/exec_utils.go#L33-L69","documentation":"The shared executor's execHelper.runTTY allocates a pseudo-terminal (pty) for an interactive exec session via newTerminal(). If opening the tty fails, the error is wrapped as 'failed to open a tty' and the exec session aborts.","triggerScenarios":"run() selects runTTY when a TTY is requested and newTerminal() (pty open, typically /dev/ptmx) fails — e.g. no free ptys, /dev/ptmx missing, or permission denied inside the container/namespace.","commonSituations":"Alloc exec with -t / interactive terminal into a container lacking /dev/pts; exhausted pty limits (devpts max); containers started without a device tree; restricted seccomp blocking pty allocation.","solutions":["Retry without a TTY (non-interactive exec) if interactivity is not required","Check /dev/pts is mounted and ptys available inside the container (mount devpts, raise max)","Verify container/devices config allows /dev/ptmx; relax seccomp if it blocks openpty","Check kernel limits (file descriptors, kernel.pty.max) on the host"],"exampleFix":"// before\nexec, err := client.AllocExec(ctx, opts) // opts.Tty = true\n// after\nif ttyUnavailable { opts.Tty = false } // fall back to non-tty exec\nexec, err := client.AllocExec(ctx, opts)","handlingStrategy":"fallback","validationCode":"// check container has pty support before requesting tty exec\nif _, err := os.Stat(\"/dev/ptmx\"); err != nil {\n    opts.Tty = false // fall back to non-tty\n}","typeGuard":null,"tryCatchPattern":"if err := execSession(); err != nil {\n    if strings.Contains(err.Error(), \"failed to open a tty\") {\n        opts.Tty = false\n        err = execSession() // retry without tty\n    }\n}","preventionTips":["Ensure /dev/pts is mounted in containers used for exec","Avoid requesting TTY for non-interactive automation","Monitor pty limits (kernel.pty.max) on busy hosts"],"tags":["tty","pty","exec","container","nomad"],"backgroundTag":"pty-allocation-failed","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}