Skip to content

Shell mode freezes / crashes the TUI and binary is not found inside the shell #3

Description

@Anand-rahul

Summary

Opening the embedded shell pane in the TUI causes the app to freeze or crash, making shell mode completely unusable. Even on systems where the freeze does not manifest, the consumer's CLI binary is not available inside the shell.

Steps to reproduce

  1. Run gotit tui in a project that has specs.
  2. Select any spec and press r to run it.
  3. Once the spec finishes (pass or fail), press Enter to open the step-review panel.
  4. Press Enter again to open the embedded shell.

Expected: an interactive shell prompt appears inside the TUI.
Actual: the TUI freezes (hangs indefinitely) — or, if it does open, the consumer binary is not on PATH.

Environment

  • Especially reproducible when $SHELL is fish or a plugin-heavy zsh.
  • Does not require any particular OS; confirmed on Linux (CachyOS / Arch).

Root causes

1 — Unread vt.Emulator response pipe deadlocks the bubbletea Update loop

vt.NewEmulator creates an unbuffered io.Pipe internally (e.pr / e.pw). When the shell queries the terminal on startup — cursor position (\e[6n), device attributes (\e[c, \e[>c), mode reports, OSC colour queries — the emulator writes responses to e.pw. Because nothing ever reads from e.pr, every such write blocks indefinitely inside emulator.Write().

emulator.Write() is called from shellModel.handleOutput, which is dispatched from the bubbletea Update loop. The entire TUI therefore deadlocks.

Fish and zsh with TERM=xterm-256color (set explicitly by shell.go) send these queries on every prompt redraw; bash with TERM=dumb does not — which is why the freeze is shell-dependent.

2 — Duplicate PTY reader on re-focus

enterShell() called waitForOutput() even when a shell for the same spec was already running. This spawned a second goroutine reading from the same PTY file descriptor, splitting the byte stream between the two readers and garbling the emulator display.

3 — Consumer binary deleted before the shell opens

testdriver.Run registers an unconditional t.Cleanup that removes binDir at test teardown. GOTIT_KEEP_HOMEDIR=1 (already used to preserve homeDir) was not applied to binDir, so the consumer CLI binary was already deleted by the time the user typed commands in the shell.

4 — Wrong shell spawned (fish / plugin-heavy zsh)

shell.go derived the shell binary from $SHELL (the user's login shell). Fish ignores PS1 and queries terminal capabilities aggressively; a heavily configured zsh does the same. For an embedded debug pane, bash is the right default.

Affected files

File Issue
tui/shell.go Unread pipe → deadlock; $SHELL used instead of bash
tui/tui.go Duplicate waitForOutput() call in enterShell()
runner/testdriver/run.go Unconditional binDir cleanup deletes the binary too early

Fix

PR #2 addresses all four root causes.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions