Skip to content

fix: replace bubbletea-based spinner with a plain ANSI spinner - #1116

Merged
cdalar merged 1 commit into
mainfrom
claude/elegant-hamilton-w7kbgq
Sep 11, 2026
Merged

cdalar merged 1 commit into
mainfrom
claude/elegant-hamilton-w7kbgq

Conversation

@cdalar

@cdalar cdalar commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #1115.

internal/ui/spinner.go was the only file in the repo importing charmbracelet/bubbletea (and transitively bubbles, lipgloss, muesli/termenv). Bubble Tea ships a package-level init() that calls lipgloss.HasDarkBackground(), which queries the terminal for its background color (OSC 11 + CSI 6n) and blocks for up to termenv's hardcoded 5s OSCTimeout if nothing answers.

Because Go runs every imported package's init() before main() regardless of which subcommand is actually invoked, this cost was paid on every onctl invocation — including version/help/anything — whenever a real terminal wasn't attached to answer the query (non-interactive ssh -tt, some pty relays/proxies, per the issue's repro).

Since this happens in a third-party package's init(), no runtime check inside onctl can intercept it in time, and no combination of import restructuring avoids it either — the onctl binary is a single statically linked binary, so any package it imports anywhere has its init() run on every invocation. The only way to actually eliminate the cost is to stop linking in the dependency that causes it.

Change

Replaced the Bubble Tea/Lip Gloss-backed Spinner with a small hand-rolled implementation that renders the same dot-spinner frames (⣾⣽⣻⢿⡿⣟⣯⣷, same color, same 10fps) directly via ANSI escape codes written to stderr. No terminal capability probing is performed at all, so nothing can block on an unresponsive pty. The public API (New, Start, Stop, Restart, SetSuffix, Active, Suffix) is unchanged, so all callers (cmd/create.go, cmd/deploy.go, cmd/destroy.go, cmd/ssh.go) needed no changes.

As a side benefit, the new spinner never reads terminal input, so it no longer needs to put stdin into raw mode — this also removes the pristine-terminal-state-capture/forced-restore-on-timeout logic that existed solely to work around Bubble Tea leaving the pty in raw mode if its own cleanup didn't finish in time (see #1086).

go mod tidy drops charmbracelet/bubbletea, charmbracelet/bubbles, charmbracelet/lipgloss, muesli/termenv, and their other transitive-only dependencies from go.mod/go.sum.

Verification

  • go build ./... and go vet ./... pass.
  • go test ./... passes (one pre-existing, unrelated failure in cmd.TestPopulateOnctlEnv_ErrorWritingFile reproduces identically on main before this change).
  • go list -deps . no longer contains any charmbracelet/* or muesli/termenv package.
  • time ./onctl version — before: paid the 5s hang in the issue's repro environment; after: unaffected regardless of pty responsiveness, since the dependency causing the query is no longer linked in at all.

Test plan

  • Unit tests in internal/ui/spinner_test.go updated for the new internal representation and passing
  • Manual smoke test of Start/SetSuffix/Stop lifecycle
  • Manual verification in a real interactive terminal that the spinner still renders/animates as expected during create/deploy/destroy (not verified here — no interactive tty available in this sandbox)

🤖 Generated with Claude Code

https://claude.ai/code/session_0116eShGQZYddF8G43ysmVnP


Generated by Claude Code

bubbletea's package init() unconditionally calls lipgloss.HasDarkBackground(),
which queries the terminal for its background color (OSC 11 + CSI 6n) and
blocks for up to termenv's 5s OSCTimeout if nothing answers. Since Go runs
every imported package's init() before main() regardless of which subcommand
is invoked, this cost was paid on every onctl invocation -- including
`version`/`help` -- once a real terminal wasn't attached to answer the query
(non-interactive ssh -tt, some pty relays/proxies).

Replace the bubbletea/bubbles/lipgloss-based Spinner with a minimal
implementation that writes the same dot-spinner frames directly via ANSI
escapes, with no terminal capability probing. This also drops the need to
put stdin into raw mode (and the associated pristine-state/restore-on-timeout
logic), since the new spinner never reads terminal input.

Fixes #1115.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116eShGQZYddF8G43ysmVnP
@cdalar
cdalar merged commit 412baf0 into main Sep 11, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

onctl pays a hardcoded 5s startup delay on non-interactive/relayed ptys (bubbletea/termenv background-color probe)

2 participants

⌂ 换网址