Skip to content

Commit bc23548

Browse files
committed
fix: reopen /dev/tty after piped stdin so interactive prompts work
1 parent 70287f4 commit bc23548

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

src/commands/create.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { readStdin, parseResults } from "../input/stdin.ts";
1919
import { buildEpicBody, bodyLengthWarning, splitBodyAtLimit } from "../output/format.ts";
2020
import { createIssue, addComment, listLabels, listIssueTemplates } from "../api/github-api.ts";
2121
import { openEditor } from "../tui/editor.ts";
22+
import { reopenStdinAsTty } from "../tui/tty.ts";
2223
import type { EpicConfig } from "../types.ts";
2324

2425
export interface CreateOptions {
@@ -61,6 +62,11 @@ export async function createAction(options: CreateOptions): Promise<void> {
6162
process.exit(1);
6263
}
6364

65+
// ── Restore TTY for interactive prompts ───────────────────────────────────
66+
// stdin was consumed by readStdin() above; re-open /dev/tty so that clack
67+
// prompts (text, confirm, select…) can still read from the terminal.
68+
reopenStdinAsTty();
69+
6470
// ── Resolve title ─────────────────────────────────────────────────────────
6571
let title = options.title;
6672
if (!title && !options.nonInteractive) {

src/tui/tty.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// ─── TTY utilities ────────────────────────────────────────────────────────────
2+
//
3+
// When stdin is piped (e.g. `cat results.md | github-issue-ops issue create`),
4+
// process.stdin is consumed by readStdin() and left at EOF.
5+
// Interactive prompt libraries (clack, inquirer, etc.) all read from
6+
// process.stdin, so they immediately receive EOF and exit silently.
7+
//
8+
// reopenStdinAsTty() replaces process.stdin with a fresh ReadStream opened
9+
// on /dev/tty, restoring the interactive terminal connection so that prompts
10+
// work normally even after piped input has been consumed.
11+
12+
import { ReadStream } from "node:tty";
13+
import { openSync } from "node:fs";
14+
15+
/**
16+
* If stdin was piped (and therefore consumed), re-open /dev/tty and reassign
17+
* process.stdin so interactive prompts can still read from the terminal.
18+
* No-op on Windows or when stdin is already a TTY.
19+
*/
20+
export function reopenStdinAsTty(): void {
21+
// Already a live TTY — nothing to do
22+
if (process.stdin.isTTY) return;
23+
// /dev/tty is POSIX-only (macOS + Linux)
24+
if (process.platform === "win32") return;
25+
try {
26+
const fd = openSync("/dev/tty", "r+");
27+
const tty = new ReadStream(fd);
28+
// @ts-expect-error — intentionally replacing the global stdin stream
29+
process.stdin = tty;
30+
process.stdin.resume();
31+
} catch {
32+
// /dev/tty unavailable (CI without terminal, container, etc.) — prompts
33+
// will still fail gracefully; caller should fall back to --non-interactive.
34+
}
35+
}

0 commit comments

Comments
 (0)