Skip to content

fix: make Quit a no-op before Run starts - #1840

Open
tianrking wants to merge 1 commit into
charmbracelet:mainfrom
tianrking:codex/quit-unstarted-lifecycle
Open

tianrking wants to merge 1 commit into
charmbracelet:mainfrom
tianrking:codex/quit-unstarted-lifecycle

Conversation

@tianrking

Copy link
Copy Markdown
  • I have read CONTRIBUTING.md.
  • I have created a discussion that was approved by a maintainer (for new features). Not applicable: this fixes an existing documented contract, with no new API.

Program.Quit() is documented as a no-op before the program starts. Currently, p := tea.NewProgram(model); p.Quit() waits for the event loop even when Run() will only be called afterward. It can therefore prevent startup altogether. Send() has a different, intentional contract: calls before startup wait until their messages can be delivered.

Track startup with a private atomic flag and make only unstarted Quit() return immediately. Set the flag before model initialization, so Quit during an active Run still goes through the normal message/filter path. Once Run returns, the canceled context retains the existing no-op behavior. Send, Kill, renderer and terminal restoration behavior are unchanged.

The regressions exercise nil/valid unstarted models, subsequent Run and message delivery, Quit during initialization, rejected and accepted Quit through WithFilter, and Send's retained pre-start waiting. All temporary goroutines have bounded, cancel-and-join cleanup. On original production with the identical final test blob, three distinct regression cases fail by assertion timeout while three active-lifecycle controls pass. The actual native test exit is 1, with race detection enabled. Original native RED.

Validation on exact source fbe2607009556aa7a7c1abe4efc32211d8a74388:

  • Linux, macOS and Windows with Go 1.26.0 and 1.27.1: all six regression cases pass, repeated ten times with race detection.
  • The full Taskfile command go test -race -count 4 -cpu 1,4 ./... passes on all six combinations. Full root build/tests/vet, tidy with an unchanged tracked tree, and scoped gofmt also pass. The original baseline passed the identical Taskfile command on Linux/Go 1.26.0.
  • All examples build and test successfully on the three platforms with Go 1.26.7 and 1.27.1, and tidy produces no tracked changes. One example package has executable tests; 62 packages have no tests. The two commented-out simple-example tests were not enabled or counted as executed tests.
  • Full configured golangci-lint v2.9.0, built with Go 1.26.0: zero issues. Govulncheck v1.8.0 reports zero reachable vulnerabilities; its uncalled imported-package/module findings are retained. Complete native checks and artifacts.

The complete-checks run has a failed overall conclusion because its first macOS PTY observer compared terminal attributes after process/session teardown. A same-platform, same-Go original Run control reproduced the only inherited-PTY attribute difference: Darwin's PENDIN flag, which the kernel sets when restoring canonical mode. Acquiring a controlling PTY also demonstrated that the slave can be revoked after the child session exits. These failed observations and their raw captures are retained, not reported as successful PTY checks.

Final PTY validation passes all eight native jobs and 16 actual executions on Linux/macOS with Go 1.26.0 and 1.27.1. It keeps the session alive after Run returns and verifies actual raw input, rendered content, q-triggered graceful exit, subsequent canonical half-line/newline behavior and echo, and exact unmasked equality of all terminal attributes before session teardown. Unchanged original Run controls and the fixed public program pass with inherited and controlling PTYs. The original control skips the already-proven blocking pre-run Quit so that its ordinary Run can be compared. Immediate post-Run attributes are also retained, including the kernel PENDIN transition. Final actual PTY evidence.

All workflow/probe files are isolated to fork helper branches. The PR changes only tea.go and its necessary lifecycle regression tests; it adds no dependencies or public API.

AI assistance was used to prepare the code and tests. Native execution, original failures, exact source/file hashes and complete-check results were independently inspected; helper status alone is not represented as a passing test result.

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.

1 participant