entireio / entireio/cli

`entire doctor` stuck-session prompt bypasses `interactive.CanPromptInteractively()`, so it blocks in scripts and CI

Open Beginner friendly
#2,416 1 comment 0 reactions 0 assignees View on GitHub

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

  1. Leave a session stuck — either ACTIVE with no interaction for over an hour, or
    ENDED with uncondensed checkpoint data on a shadow branch.
  2. Call entire doctor from 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
    feed interactive.CanPromptInteractively()
    (cmd/entire/cli/interactive/interactive.go), which this path never calls. We
    set GIT_TERMINAL_PROMPT=0 and it correctly suppresses the checkGitHooks
    confirmation while leaving this one blocking.
  • --force is not a read-only substitute. Per entire doctor --help: "Use
    --force to 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:

  1. promptSessionAction (:324) does not route its error through
    handleFormCancellation (utils.go:35), so huh.ErrTimeout becomes a hard
    error where the caller only special-cases huh.ErrUserAborted (:212).
  2. 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_active more than an hour ago — and note status
    carries idle as well as active, 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.