fix(review): honor agent prompt sentinels · Entire

fix(review): honor agent prompt sentinels

dfd203cmain·

dipree·3d ago·2 files·+19 added/-15 removed

Sessions

01KXGT7DJ8E7EC047SBCZXN1Z3View transcript

[?
Fix Review Interactive Setup and DefaultsPi·GPT-5.6-sol·1 step](/content/gh/entireio/cli/session/019f60f0-7ee7-73b0-b787-4c8cbc646802#timeline-01KXGT7DJ8E7EC047SBCZXN1Z3/index.html)

Changes

2

269 unmodified lines

270
271
272
273
274
275
276
277
278
273
274
275
276
277
278
279
3 unmodified lines

283
284
285
286
287
288
289
8 unmodified lines

298
299
300
302
303
301
302
303
304
305
306
307
308

269 unmodified lines

Slots  []string // reviewer slots as "agent[=model]" entries (--set-slot)
}

// 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, 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.
// reviewCommandIsInteractive requires the exact stdin consumed by huh and
// Bubble Tea, plus stdout, to be terminals. CanPromptInteractively adds the
// independent policy gate for tests, CI, and agent subprocess sentinels; a
// controlling /dev/tty alone is insufficient because stdin may still be piped.
func reviewCommandIsInteractive(cmd *cobra.Command) bool {
    hardDisabled := reviewInteractivityHardDisabled(
        os.Getenv(interactive.EnvTestTTY),

3 unmodified lines

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

8 unmodified lines

return underTest || (ci != "" && ci != "false")
}

func reviewTTYIsInteractive(stdinTTY, stdoutTTY, hardDisabled bool) bool {
    return !hardDisabled && stdinTTY && stdoutTTY
func reviewTTYIsInteractive(stdinTTY, stdoutTTY, canPrompt, hardDisabled bool) bool {
    // Real stdio terminals are necessary but not sufficient: agent shells can
    // allocate a PTY while advertising that no human is available through the
    // sentinels enforced by CanPromptInteractively.
    return !hardDisabled && stdinTTY && stdoutTTY && canPrompt
}

func (o reviewConfigureOptions) scripted() bool {

Mcmd/entire/cli/review/cmd.go+10/-8

121 unmodified lines

122
123
124
125
126
127
128
128
129
130
131
132
129
130
131
132
133
134
135
136
137
138
137
138
139
140
141
142
143

121 unmodified lines

name         string
    stdinTTY     bool
    stdoutTTY    bool
    canPrompt    bool
    hardDisabled bool
    want         bool
    }{
    {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},
    {name: "direct human terminal", stdinTTY: true, stdoutTTY: true, canPrompt: true, want: true},
    {name: "agent sentinel overrides real PTY", stdinTTY: true, stdoutTTY: true, canPrompt: false, want: false},
    {name: "controlling terminal does not override piped stdin", stdinTTY: false, stdoutTTY: true, canPrompt: true, want: false},
    {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: "explicitly forced non-interactive", stdinTTY: true, stdoutTTY: true, canPrompt: 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.hardDisabled); got != tt.want {
            t.Fatalf("reviewTTYIsInteractive(%v, %v, %v) = %v, want %v", tt.stdinTTY, tt.stdoutTTY, tt.hardDisabled, got, tt.want)
        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)
        }
        }
    }
}`