`entire doctor` stuck-session prompt bypasses `interactive.CanPromptInteractively()`, so it blocks in scripts and CI
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 475
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 178
Description
Summary
On any clone holding at least one stuck session, entire doctor asks
Condense/Discard/Skip through a huh form. huh/bubbletea opens /dev/tty
directly rather than reading stdin, so redirecting or closing stdin does not
reach it. Called from a script that captures output, the menu is therefore
invisible and the command waits on a keystroke for a screen nobody can see.
The same file already solves this for its other prompt. checkGitHooks
(cmd/entire/cli/doctor.go:533) guards its confirmation with
interactive.CanPromptInteractively() at :574 and degrades to naming
entire doctor --force. The stuck-session loop in runSessionsFix (:94)
calls promptSessionAction at :210 with no such check — and :574 is the
only interactive. reference in the file.
So this reads as an inconsistency rather than a missing feature: the gate exists,
is centralised (#601), and is used 360 lines below the prompt that needs it.
Version
- Entire CLI 0.10.2 (
go1.26.6,darwin/arm64), macOS 15.6 / Darwin 25.6.0 - Source read at tag
v0.10.2
Reproduction
- Leave a session stuck — either ACTIVE with no interaction for over an hour, or
ENDED with uncondensed checkpoint data on a shadow branch. - Call
entire doctorfrom a script that captures its output, at a real
terminal:
report=$(entire doctor 2>&1 </dev/null)
Expected: the report is captured, the findings are readable, and anything
needing a human is named as a command to run — which is what :574 does.
Actual: it blocks indefinitely. The form renders into the command
substitution, so the terminal shows nothing at all.
On a machine with no controlling terminal the same path fails instead, and the
failure is reported as though it were a finding:
Found 1 stuck session(s):
Session: 44b4e5fd-5a32-4e6b-b007-87001418b438
Phase: ended
Reason: ended with uncondensed checkpoint data
Shadow branch: exists (entire/585dd05-e3b0c4)
Checkpoints: 1, Files touched: 1
failed to get action: session fix prompt failed: huh: bubbletea: error opening TTY: bubbletea: could not open TTY: open /dev/tty: device not configured
Exit status is 0 in both cases.
Why the existing escape hatches do not apply
GIT_TERMINAL_PROMPT=0,CI=1, a raw-mode controlling terminal. All three
feedinteractive.CanPromptInteractively()
(cmd/entire/cli/interactive/interactive.go), which this path never calls. We
setGIT_TERMINAL_PROMPT=0and it correctly suppresses thecheckGitHooks
confirmation while leaving this one blocking.--forceis not a read-only substitute. Perentire doctor --help: "Use
--forceto condense all fixable sessions without prompting. Sessions that
can't be condensed will be discarded." A health check cannot adopt a flag that
discards session data.NewAccessibleForm(cmd/entire/cli/utils.go:26) is a thin wrapper over
uiform.New, so there is no gate inside the form construction either.
CI is worth calling out: CanPromptInteractively() returns false there
deliberately, so this is precisely the environment the gate was added for, and
this prompt escapes it.
Suggested fix
Guard :210 the way :574 is guarded, and mirror its wording:
// Interactive: prompt for action
if !interactive.CanPromptInteractively() {
fmt.Fprintln(cmd.OutOrStdout(), " Run `entire doctor` to fix it, or `entire doctor --force` to condense what can be condensed and discard the rest.")
continue
}
action, err := promptSessionAction(ss)
The diagnostic block above it (displayStuckSession, :300) already prints
everything a caller needs, so the non-interactive path loses no information — it
only stops asking a question nobody can answer.
Two smaller things in the same area, mentioned in case they are wanted here
rather than separately:
promptSessionAction(:324) does not route its error through
handleFormCancellation(utils.go:35), sohuh.ErrTimeoutbecomes a hard
error where the caller only special-caseshuh.ErrUserAborted(:212).- When the prompt does fail, the message surfaces as a finding rather than as a
diagnostic failure, which makes the corpse of the prompt look like a second
problem with the repository.
Workaround, for anyone else shelling out to the CLI
Read the stuck sessions from entire session list --json and apply the rule from
entire doctor --help, then call entire doctor only when nothing is stuck:
- ENDED and stuck:
status == "ended",checkpoints > 0, and no
last_checkpoint_id. - ACTIVE and stuck:
last_activemore than an hour ago — and notestatus
carriesidleas well asactive, both of which are ACTIVE-phase.
Checked against the CLI's own verdict in both directions on one clone: while it
reported one stuck session this selected exactly that session out of 23, and when
it reported none it selected none out of 22.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in cmd/entire/cli/doctor.go at runSessionsFix and promptSessionAction, then compare the existing interactive.CanPromptInteractively() guard in checkGitHooks. Reproduce with entire doctor using captured output and closed stdin; done means stuck-session checks no longer open a prompt non-interactively, while interactive behavior remains unchanged and the suggested command is printed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 86/100