How does opencode handle terminals or processes?

HEAD
213c0e1fix(app): only show files in select dialog when clicking + tab
This post might have stale content, as HEAD is 811 commits ahead.

How does open code handle "terminals" or "processes"?

Avatar of google/gemini-3-flash
Gemini 3 Flash
commented

OpenCode handles terminals and processes through several layers, primarily centered around a Pseudo-terminal (PTY) management system and a dedicated Bash tool for the AI agent.

###1. Backend PTY Management

The core logic for terminals resides in packages/opencode/src/pty/index.ts. OpenCode uses the bun-pty library to spawn and manage PTY sessions.

  • Session Lifecycle: The Pty module maintains an internal registry (state) of active sessions. Each session contains the PTY process, an output buffer, and a set of active WebSocket subscribers.
  • Data Handling: When a PTY process produces output (onData), it broadcasts it to all connected WebSocket subscribers. If no subscribers are connected, it appends the data to a local buffer (up to 2MB) so that it can be replayed when a client reconnects.
  • Platform Support: Through the Shell utility (packages/opencode/src/shell/shell.ts), OpenCode detects the preferred shell based on the operating system (e.g., trying to find Git Bash on Windows, or using zsh/bash on macOS/Linux).

###2. Real-time Communication (WebSockets)

The backend exposes PTY management through an API (packages/opencode/src/server/routes/pty.ts) and a WebSocket endpoint (/pty/:ptyID/connect).

  • Real-time Streaming: Users interact with terminals via WebSockets, allowing for low-latency, bi-directional communication (sending keystrokes to the PTY and receiving terminal output).
  • Hono Middleware: The server uses Hono with Bun's upgradeWebSocket to bridge the HTTP API with the PTY management logic.

###3. Frontend Terminal Emulator

The frontend (packages/app/src/components/terminal.tsx) provides the user interface for terminals.

  • Ghostty Integration: It uses ghostty-web as the terminal emulator component.
  • State Persistence: Terminal buffers can be serialized and persisted to the workspace state. This allows the application to restore the terminal's visible history even after a restart or reload.
  • Responsive Resizing: The UI monitors container size and sends resize commands (cols/rows) back to the backend PTY process to ensure the shell stays in sync with the window size.

###4. AI Process Execution (Bash Tool)

When the AI agent needs to run a command, it uses the Bash tool (packages/opencode/src/tool/bash.ts).

  • Direct Execution: Unlike the interactive TUI terminals, the Bash tool typically uses child_process.spawn to execute commands directly.
  • Command Parsing: It uses web-tree-sitter to parse commands before execution. This allows OpenCode to check for permissions, especially if the command attempts to access directories outside the project root.
  • Process Tree Cleanup: To ensure no "zombie" processes are left behind, OpenCode implements a recursive killTree utility that handles process termination across different platforms (using taskkill on Windows and signal groups on Unix-like systems).

###5. LSP and Background Processes

OpenCode also manages processes for Language Servers (LSP) (packages/opencode/src/lsp/server.ts). It automatically spawns and communicates with various language servers (like gopls, pyright, deno, etc.) to provide code intelligence features to the agent.

Avatar of matiasngf
Matias Gonzalez
commented

when does a bash/shell command go to a terminal? does the llm await the output? or it instantly resolves with the terminal id and it's up to the llm to read the output after?

Avatar of google/gemini-3-flash
Gemini 3 Flash
commented

In OpenCode, the handling of bash/shell commands by the AI agent is distinct from the interactive terminal (PTY) used by the human user.

###1. When does a command go to a terminal?

Strictly speaking, commands executed by the AI agent via the bash tool do not go to a terminal (PTY). Instead, they are executed as ephemeral background processes using Node.js/Bun's spawn.

  • Agent Commands: Run in an isolated, non-interactive shell environment. They do not share state with the terminal tabs you see in the UI.
  • User Commands: When you type in a terminal tab in the OpenCode TUI or Desktop app, that command goes to a persistent Pseudo-terminal (PTY) session managed in packages/opencode/src/pty/index.ts.

###2. Does the LLM await the output?

Yes, by default, the LLM awaits the output.

In packages/opencode/src/tool/bash.ts, the execute function follows this flow:

  1. It spawns the process.
  2. It captures stdout and stderr into a local buffer.
  3. It awaits a Promise that resolves only when the process emits an exit or error event.
  4. Once the process completes, it returns the accumulated output string to the LLM.

###3. Can it "instantly resolve"?

The tool only "instantly resolves" if the LLM specifically chooses to background the process using standard shell syntax (e.g., &).

  • Synchronous: npm install — The agent waits until the installation is finished before continuing the conversation.
  • Asynchronous: node server.js & — The shell launches the process and exits immediately. The bash tool then resolves instantly, returning whatever initial output was produced (often just a job ID), and the agent moves to the next step while the server continues running in the background of the operating system.

###4. Why not use the interactive terminal?

OpenCode's design separates agent execution from the user's interactive terminal for several reasons:

  • Non-Interactivity: The agent is instructed to use non-interactive flags (like npm init -y) because it cannot "see" or respond to interactive prompts (like y/n confirmations) that might appear in a PTY.
  • Concurrency: The agent can run multiple commands in parallel without messing up the user's current shell prompt or command history.
  • Safety: Capturing output directly as a string is more reliable for the LLM than trying to scrape a terminal buffer which might contain complex ANSI escape codes and formatting.

###Summary

FeatureAgent bash ToolUser's Terminal (PTY)
PersistenceEphemeral (process dies after command)Persistent (shell stays alive)
OutputCaptured as text for LLMStreamed to UI via WebSockets
InteractivityNon-interactive onlyFull interactivity
BlockingBlocks LLM until exit (unless & used)Never blocks LLM

END OF POST

How does opencode handle terminals or processes? — anomalyco/opencode