# fix(review): require terminal stdin for interactive mode

`1ac87bc`→[main](/content/gh/entireio/cli/commits/main/index.html)·

dipree·3d ago·2 files·+11 added/-12 removed

## Sessions

01KXGSC7YR34K5TTMAVFH7PCMAView transcript

## Changes

2

- cmd/entire/cli/review

- Mcmd.go+5/-5

- Mconfigure_test.go+6/-7

```
272 unmodified lines

273
274
275
276
277
276
277
278
279
280
281
1 unmodified line

283
284
285
285
286
287
288
289
290
291
290
291
292
293
294

272 unmodified lines

// reviewCommandIsInteractive treats a real terminal on both stdin and stdout
// as authoritative for this explicitly user-invoked command. This avoids
// suppressing the review wizard when a normal shell inherits an agent sentinel
// or GIT_TERMINAL_PROMPT=0. The fallback preserves controlling-TTY detection
// for callers whose stdio is not wired directly to the terminal.
// or GIT_TERMINAL_PROMPT=0, while requiring the exact stdin consumed by huh and
// Bubble Tea to accept keypresses. A controlling /dev/tty alone is insufficient
// because the command may still have piped stdin.
func reviewCommandIsInteractive(cmd *cobra.Command) bool {
	testTTY := os.Getenv(interactive.EnvTestTTY)
	ci := os.Getenv("CI")
1 unmodified line

return reviewTTYIsInteractive(
		interactive.IsTerminalReader(cmd.InOrStdin()),
		interactive.IsTerminalWriter(cmd.OutOrStdout()),
		interactive.CanPromptInteractively(),
		hardDisabled,
	)
}

func reviewTTYIsInteractive(stdinTTY, stdoutTTY, canPrompt, hardDisabled bool) bool {
	return !hardDisabled && stdoutTTY && (stdinTTY || canPrompt)
}
func reviewTTYIsInteractive(stdinTTY, stdoutTTY, hardDisabled bool) bool {
	return !hardDisabled && stdinTTY && stdoutTTY
}

func (o reviewConfigureOptions) scripted() bool {
```

Mcmd/entire/cli/review/cmd.go+5/-5

```
94 unmodified lines

95
96
97
98
98
99
100
102
103
104
105
101
102
103
104
105
106
107
108
109
111
112
110
111
112
113
114

94 unmodified lines

name         string
	stdinTTY     bool
	stdoutTTY    bool
	canPrompt    bool
	hardDisabled bool
	want         bool
	}{
	{name: "direct terminal overrides inherited sentinel", stdinTTY: true, stdoutTTY: true, canPrompt: false, want: true},
	{name: "controlling terminal fallback", stdinTTY: false, stdoutTTY: true, canPrompt: true, want: true},
	{name: "captured stdout", stdinTTY: true, stdoutTTY: false, canPrompt: true, want: false},
	{name: "agent with piped stdin", stdinTTY: false, stdoutTTY: true, canPrompt: false, want: false},
	{name: "direct terminal overrides inherited sentinel", stdinTTY: true, stdoutTTY: true, want: true},
	{name: "controlling terminal does not override piped stdin", stdinTTY: false, stdoutTTY: true, want: false},
	{name: "captured stdout", stdinTTY: true, stdoutTTY: false, want: false},
	{name: "agent with piped stdin", stdinTTY: false, stdoutTTY: true, want: false},
	{name: "explicitly forced non-interactive", stdinTTY: true, stdoutTTY: true, hardDisabled: true, want: false},
}
for _, tt := range tests {
	t.Run(tt.name, func(t *testing.T) {
		t.Parallel()
		if got := reviewTTYIsInteractive(tt.stdinTTY, tt.stdoutTTY, tt.canPrompt, tt.hardDisabled); got != tt.want {
			t.Fatalf("reviewTTYIsInteractive(%v, %v, %v, %v) = %v, want %v", tt.stdinTTY, tt.stdoutTTY, tt.canPrompt, tt.hardDisabled, got, tt.want)
		if got := reviewTTYIsInteractive(tt.stdinTTY, tt.stdoutTTY, tt.hardDisabled); got != tt.want {
			t.Fatalf("reviewTTYIsInteractive(%v, %v, %v) = %v, want %v", tt.stdinTTY, tt.stdoutTTY, tt.hardDisabled, got, tt.want)
		}
		}
}
```

Mcmd/entire/cli/review/configure_test.go+6/-7

```
