Open-Source Wikis

/

Bubble Tea

/

Features

/

Exec subprocess

charmbracelet/bubbletea

Exec subprocess

Active contributors: meowgorithm, aymanbagabas

Purpose

tea.Exec lets a Bubble Tea program hand the terminal over to a child process — typically an interactive program like $EDITOR, git rebase -i, or less. The runtime releases raw mode, runs the child, then reclaims the terminal when it exits.

API

exec.go:

// High level: pass an *exec.Cmd
cmd := tea.ExecProcess(exec.Command("vim", "file.txt"), func(err error) tea.Msg {
    return EditorFinishedMsg{err: err}
})
return m, cmd

// Low level: pass any ExecCommand implementation
type ExecCommand interface {
    Run() error
    SetStdin(io.Reader)
    SetStdout(io.Writer)
    SetStderr(io.Writer)
}

ExecProcess wraps an *exec.Cmd in osExecCommand, which sets stdin/stdout/stderr to the program's terminal if they are not already configured.

How it works

sequenceDiagram
    participant M as Model
    participant L as eventLoop
    participant P as Program
    participant C as Child process
    M->>L: tea.ExecProcess(cmd, cb) returns Cmd
    L->>P: execMsg{cmd, fn}
    P->>P: releaseTerminal(false)
    P->>P: SetStdin/Stdout/Stderr to TTY
    P->>C: cmd.Run() (blocks event loop)
    C-->>P: exit status
    P->>P: RestoreTerminal()
    P-->>M: cb(err) Msg
    L->>M: forwarded as normal Msg

The key call chain (in tea.go's eventLoop):

case execMsg:
    p.exec(msg.cmd, msg.fn)

Program.exec (exec.go):

  1. p.releaseTerminal(false) — flushes pending output, restores TTY state, cancels the input reader.
  2. Configures the child's stdio to share the program's TTY.
  3. c.Run() — blocks until the child exits. eventLoop is paused; signals still arrive but their Msgs are buffered.
  4. p.RestoreTerminal() — re-enters raw mode, restarts the renderer (which replays mode flags from the last View), reinitializes the input reader, and triggers a checkResize since the terminal may have been resized while the child ran.
  5. The user-supplied callback runs with the child's error and produces the Msg that completes the round trip.

Examples

examples/exec/main.go shows opening $EDITOR on key press. The pattern:

case tea.KeyPressMsg:
    if msg.String() == "e" {
        return m, tea.ExecProcess(exec.Command("vim", "tmp.txt"), editorFinishedMsg)
    }

Caveats

  • Don't use for non-interactive I/O. A regular Cmd (i.e. a tea.Cmd) handles HTTP requests, file reads, and DB calls. Exec is for things that need the TTY.
  • No mid-command cancellation. Once the child starts, the eventLoop pauses. Use os/exec's context-based start for cancellable children — but you still cannot redraw the parent UI while the child is running.
  • Windows TTY semantics. The child must understand the same console the parent uses; Bubble Tea does not allocate a new pseudo-terminal.
  • Stdin sharing. If the child reads from stdin, it shares the program's input file. Anything the user types during the child goes to the child, not the model.
  • Program runtimereleaseTerminal/RestoreTerminal are the same primitives used by suspend/resume.
  • CommandsExec is technically a Cmd.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Exec subprocess – Bubble Tea wiki | Factory