{"record":{"id":"b65521184d84d9c4","repo":"git-ecosystem/git-credential-manager","slug":"failed-to-open-ttydevicename-for-writing","errorCode":null,"errorMessage":"Failed to open {TtyDeviceName} for writing.","messagePattern":"Failed to open (.+?) for writing\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"src/Core/Interop/Posix/PosixAnsiConsoleOutput.cs","lineNumber":34,"sourceCode":"/// the object lifetime. Construction throws <see cref=\"IOException\"/> when\n/// the device is not available (typically a headless invocation).\n/// </remarks>\npublic sealed class PosixAnsiConsoleOutput : IAnsiConsoleOutput, IDisposable\n{\n    private const string TtyDeviceName = \"/dev/tty\";\n\n    private readonly int _fd;\n    private readonly SafeFileHandle _handle;\n    private readonly StreamWriter _writer;\n\n    public PosixAnsiConsoleOutput()\n    {\n        PlatformUtils.EnsurePosix();\n\n        _fd = Fcntl.open(TtyDeviceName, OpenFlags.O_WRONLY);\n        if (_fd == -1)\n        {\n            throw new IOException($\"Failed to open {TtyDeviceName} for writing.\");\n        }\n\n        _handle = new SafeFileHandle(new IntPtr(_fd), ownsHandle: true);\n\n        // Stream wraps the SafeFileHandle and will close it on disposal.\n        var stream = new FileStream(_handle, FileAccess.Write);\n        _writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))\n        {\n            AutoFlush = true,\n        };\n    }\n\n    public TextWriter Writer => _writer;\n\n    public bool IsTerminal => true;\n\n    public int Width => SafeWindowWidth();\n","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/git-ecosystem/git-credential-manager/blob/e8ce762cd04b4100ae637b5fbf39ef9d0a96561e/src/Core/Interop/Posix/PosixAnsiConsoleOutput.cs#L16-L52","documentation":"PosixAnsiConsoleOutput's constructor opens the terminal device (e.g. /dev/tty) write-only via fcntl open to render ANSI output. If open returns -1 it throws IOException, since ANSI prompts and messages cannot be written without a terminal handle.","triggerScenarios":"Constructing PosixAnsiConsoleOutput when TtyDeviceName cannot be opened with O_WRONLY: no controlling terminal or permission denied on the device.","commonSituations":"Same environments as input-side TTY failures: cron/CI jobs without a TTY, detached daemons, containers without -t, restricted /dev/tty permissions.","solutions":["Run within a session that has a controlling terminal (interactive shell, `docker run -it`).","Disable interactive/GUI prompting (GCM_INTERACTIVE=never) and use a non-terminal credential store.","Verify /dev/tty exists and the user has write access; check for restricted device cgroups in containers.","Redirect the helper's output to a real terminal for debugging: `script -qc 'git push' /dev/null`."],"exampleFix":"// before (cron job, no TTY)\n*/5 * * * * git fetch   # IOException: failed to open /dev/tty for writing\n// after\n*/5 * * * * GCM_INTERACTIVE=never git fetch  # or run inside tmux/screen session","handlingStrategy":"try-catch","validationCode":"bool CanWriteTty() => Fcntl.access(TtyDeviceName, AccessMode.W_OK) == 0;","typeGuard":null,"tryCatchPattern":"try { using var output = new PosixAnsiConsoleOutput(); ... }\ncatch (IOException ex) when (ex.Message.Contains(\"Failed to open\"))\n{ logger.Warn(\"No TTY available for output; disabling interactive prompts.\"); }\n","preventionTips":["Run interactive git operations only in sessions with a controlling terminal.","Set GCM_INTERACTIVE=never for scheduled/CI jobs before they spawn git.","Check /dev/tty permissions and container device cgroups when running in Docker.","Use tmux/screen or `docker run -it` to guarantee a TTY."],"tags":["posix","tty","io","terminal"],"backgroundTag":"file-open-failed","analyzedSha":"e8ce762cd04b4100ae637b5fbf39ef9d0a96561e","analyzedAt":"2026-09-11T17:15:08.753Z","contentChangedAt":"2026-09-11T17:15:08.753Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}