This guide explains how to install, configure, start, use, monitor, stop, upgrade, and troubleshoot Codex Web Terminal from a verified release archive or a local source build.
See BUILDING.md for compilation and packaging. See README.md for architecture, protocol, and API details.
Codex Web Terminal is remote terminal access. It runs with the permissions and environment of the operating-system user that starts it. A browser holding the authentication token can:
- type into the selected agent terminal;
- create, attach to, restart, or terminate managed sessions;
- list server filesystem roots, browse readable directories, and resolve an absolute server path;
- read server-wide Favorites/Recent and add or remove Favorites; successful launches update Recent;
- launch an agent in any directory readable by the server account;
- respond to approval prompts;
- cause an agent to read or modify files allowed to the server user.
Treat the authenticated URL as a credential.
Safe defaults:
- bind to
127.0.0.1; - use a newly generated token;
- use Tailscale for access from another device;
- restrict the Tailscale ACL to intended users and devices;
- never expose the port directly to the public Internet;
- never commit or log the token.
Before starting the server, verify the primary CLI as the same
operating-system user that will run codex-web:
codex --version
codex login
Claude Code and AGY are optional. When installed, verify them with:
claude --version
agy --version
The server performs the same read-only version probes during agent discovery. It disables each provider's documented automatic updater only for that probe, removes parent-agent nesting markers, enforces a three-second deadline, and publishes only a strictly validated semantic version. On Windows the process is created suspended, assigned to a kill-on-close Job Object, and then resumed; on Unix it runs in a dedicated process group. Descendants therefore cannot outlive a failed or completed probe. Normal interactive agent sessions keep their usual updater behavior. The server does not copy or manage agent authentication. Spawned processes inherit the current user's environment and use that user's existing CLI configuration and credentials.
Choose the default project directory deliberately:
--project /absolute/path/to/project
The backend canonicalizes this path, verifies that it is a readable directory,
and starts the primary terminal there. It is also the fallback for a
new-session API request that omits directoryId.
--project is not a filesystem sandbox or allowlist. An authenticated browser
can use New terminal to select another absolute directory readable by the
operating-system account running the server. The selected directory applies
only to that new managed terminal. The server canonicalizes and checks it
again at browse and launch time.
Installation and updates happen on the server host, not on the phone,
laptop, or browser used to view the terminal. The New dialog reports each
agent as ready, missing, or misconfigured, displays its installed version
when available, and provides a platform-specific manual command. After running
that command in a trusted host terminal, select Refresh or Check again
to repeat detection.
Official native installation commands:
| CLI | Windows PowerShell | Linux/macOS |
|---|---|---|
| Codex | powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex" |
curl -fsSL https://chatgpt.com/codex/install.sh | sh |
| Claude Code | irm https://claude.ai/install.ps1 | iex |
curl -fsSL https://claude.ai/install.sh | bash |
| AGY | irm https://antigravity.google/cli/install.ps1 | iex |
curl -fsSL https://antigravity.google/cli/install.sh | bash |
Official update commands:
| CLI | Native installation | Package-manager note |
|---|---|---|
| Codex | rerun the platform installer shown by Check again | For an npm installation: npm install --global @openai/codex@latest |
| Claude Code | claude update |
WinGet: winget upgrade Anthropic.ClaudeCode; Homebrew: brew upgrade claude-code |
| AGY | Re-run the platform install command; it installs or upgrades | AGY also checks for background updates unless AGY_CLI_DISABLE_AUTO_UPDATE=true |
Verify the current upstream instructions before executing a downloaded script: Codex CLI, Claude Code, and Antigravity CLI.
There is deliberately no silent browser-side Install button and no install
API. A browser session has full terminal input authority already; allowing it
to install or replace host executables would create an additional supply-chain
and privilege boundary. Downloaded scripts may change, invoke package
managers, modify PATH, prompt for authentication, or require local policy
review. The operator must review and run them explicitly. Refresh and
Check again perform only fixed executable discovery and bounded
--version probes.
Download the Windows or Linux archive and SHA256SUMS.txt from the official
GitHub Releases page. A
release archive is accepted only after Windows/Linux tests, the packaged
@cwt regression, and target-specific third-party license generation pass.
Do not use an archive copied from a local dist directory or an unofficial
mirror.
Verify the archive before extraction. On Linux:
grep -F ' codex-web-terminal-vX.Y.Z-linux-x86_64-glibc.tar.gz' \
SHA256SUMS.txt | sha256sum -c -
gh attestation verify \
--repo bproject07/Codex-web \
--signer-workflow bproject07/Codex-web/.github/workflows/release.yml \
codex-web-terminal-vX.Y.Z-linux-x86_64-glibc.tar.gzOn Windows, compare Get-FileHash -Algorithm SHA256 <archive.zip> with the
matching line in SHA256SUMS.txt, then run:
gh attestation verify `
--repo bproject07/Codex-web `
--signer-workflow bproject07/Codex-web/.github/workflows/release.yml `
.\codex-web-terminal-vX.Y.Z-windows-x86_64.zipExtract the whole versioned directory. Keep web and
THIRD_PARTY_LICENSES beside the executable. Windows packages are not
Authenticode-signed yet and may trigger SmartScreen's unknown-publisher
warning; checksum and provenance verification are required before choosing to
run an unsigned archive. The Linux artifact is built on Ubuntu 22.04 for
x86_64 glibc 2.35 or newer; it is not a musl or universal Linux build.
Official packages also contain release-package.json. Do not copy that marker
into a source build: it is generated only after the release workflow validates
the complete target package. Before publication, the workflow requires the
repository immutability policy and exact GitHub SHA-256 digests for the
Windows archive, Linux archive, and checksum file. After publication, it
requires the release itself to report immutable with the same exact asset
names, sizes, and digests; an incomplete release is not updater-eligible.
For a source checkout, build first:
.\scripts\build.ps1Start on loopback with an automatically generated token:
.\scripts\run.ps1 `
-Project "C:\Projects\my-app" `
-ListenHost "127.0.0.1" `
-Port 8787The server prints an authenticated URL. Open the complete URL in the browser.
The frontend normally moves the token into the current tab's sessionStorage
and removes it from the visible address bar.
For a source checkout, build first:
./scripts/build.shStart on loopback:
./scripts/run.sh "/home/user/projects/my-app" \
--host 127.0.0.1 \
--port 8787Or run the package directly:
./dist-linux/codex-web \
--project "/home/user/projects/my-app" \
--host 127.0.0.1 \
--port 8787If --token and CODEX_WEB_TOKEN are both omitted, the server generates a
strong ephemeral token. It changes on every server restart.
Use an explicit token when a bookmark, service, or planned reconnect must keep working across restarts. Tokens must:
- contain at least 16 characters;
- contain no whitespace;
- remain at or below 512 bytes.
Generate a URL-safe 256-bit token on Windows PowerShell:
$tokenBytes = New-Object byte[] 32
$generator = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$generator.GetBytes($tokenBytes)
$generator.Dispose()
$env:CODEX_WEB_TOKEN = [Convert]::ToBase64String($tokenBytes).
TrimEnd("=").
Replace("+", "-").
Replace("/", "_")Generate one on Linux:
export CODEX_WEB_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"Prefer an environment variable over a command-line token on multi-user machines because process command lines can be visible to other local users. For a service, store the token in a permission-restricted environment file.
The browser normally stores the token only in the current tab's
sessionStorage. If hardened browser settings reject that storage, the current
page keeps it only in memory. A controlled application-update reload then uses
the token in a same-origin query for one navigation and removes it from the
visible address again when the frontend starts. Closing the tab or choosing
Forget token removes the browser copy and any token query. It does not stop
the server or change the server-side token.
The folder picker's Favorites and Recent entries are host- and operating-system-account-local, server-wide state. They are shared by every browser that authenticates to this server and are stored in:
<state-directory>/workspaces.json
Select the state directory with --state-dir or
CODEX_WEB_STATE_DIR. Defaults:
Windows: %LOCALAPPDATA%\codex-web-terminal
or %USERPROFILE%\AppData\Local\codex-web-terminal
Unix: $XDG_STATE_HOME/codex-web-terminal
or $HOME/.local/state/codex-web-terminal
Windows uses LOCALAPPDATA when it exists; the USERPROFILE path is used only
when it does not. On Unix,
XDG_STATE_HOME must be absolute or the $HOME/.local/state fallback is
used. A relative explicit value is resolved against the working directory
from which the server starts.
For run.ps1, set the environment variable:
$env:CODEX_WEB_STATE_DIR = Join-Path $env:LOCALAPPDATA "codex-web-terminal-instance-1"
.\scripts\run.ps1 -Project "C:\Projects\my-app"run.sh also accepts the backend option after its project argument:
./scripts/run.sh "/srv/projects/default" \
--state-dir "$HOME/.local/state/codex-web-terminal-instance-1"The file uses schema version 1, is limited to 32 MiB (33,554,432 bytes) on both read and write, and stores at most 100 Favorites and 30 Recent folders. Recent is deduplicated by native directory, ordered newest first, and records the actual agent used. A successful primary startup, New launch, or restart updates Recent; it also updates the preferred agent of an existing Favorite for that directory. If this post-launch save fails, the PTY remains live and the server logs a warning. A Favorite mutation that would serialize beyond the limit is rejected with HTTP 507 before the current file or in-memory state is replaced.
Writes use a new temporary file in the same directory, flush it, and
atomically replace workspaces.json. Unix also syncs the parent directory and
creates a missing final state directory with mode 0700 and new state files
with 0600. An existing Unix directory or file must already be owned by the
effective server user and grant no group/other permissions. The server rejects
unsafe existing targets instead of changing their mode. On Windows, protect
the chosen directory with an ACL appropriate for the service account.
The state location must be a dedicated real directory. Filesystem roots,
known broad account/system locations, the current or system temporary
directory, symlinks, and Windows reparse points are rejected. The existing
workspaces.json, when present, must be a regular non-link file. An unsafe
location prevents server startup rather than being silently repaired.
The store coordinates writers only inside one process; it has no cross-process
lock or merge. Assign a distinct --state-dir to every server instance that
can run concurrently. Two instances sharing one file can overwrite each
other's newer Favorites or Recent updates.
At startup, malformed JSON, an unsupported version, invalid records, or a file
larger than 32 MiB is renamed to
workspaces.corrupt.<uuid>.json. The server logs a warning and continues with
a clean schema-1 library; normal successful primary startup may immediately
add the default folder to Recent. The quarantined bytes are preserved and are
not overwritten. Do not publish that file because it contains filesystem
paths and usage history.
For a consistent backup, stop the server and copy workspaces.json together
with any quarantined files you intend to retain. Restore only a reviewed
schema-1 file while the server is stopped. Ensure the service account owns the
directory and can create, replace, and rename files inside it. Both display
paths and reversible native path IDs can reveal filesystem layout, so protect
backups with the same care as the live file.
Saved paths are not continuously monitored. A renamed, deleted, or newly
restricted folder may remain visible in Favorites or Recent. Opening,
updating, or launching from it performs a fresh canonicalization and read
check; a missing path returns 404, and an inaccessible path returns 403.
Restart performs the same launch-time validation before terminating the
running PTY, and also rejects a path that now resolves through a symlink or
junction to a different canonical directory. Remove the stale Favorite or
browse to the new location.
| Option | Default | Meaning |
|---|---|---|
--host |
127.0.0.1 |
Address on which the HTTP server listens |
--port |
8787 |
TCP port |
--max-sessions |
20 |
Managed capacity from 1 through 256, including the primary entry, stopped entries, and dedicated @cwt reviewers |
--project |
current directory | Default working directory for the primary PTY and new sessions without a selected folder |
--state-dir |
per-user OS state directory | Dedicated directory containing workspaces.json Favorites/Recent state |
--command |
derived | Explicit primary executable override; otherwise follows --primary-agent |
--primary-agent |
codex |
Agent represented by --command: codex, claude, or agy |
--new-session-command |
resolved primary command | Optional executable used when New starts the primary agent |
--codex-command |
unset | Explicit Codex CLI executable override |
--claude-command |
unset | Explicit Claude Code executable override |
--claude-dangerously-skip-permissions |
off | Start Claude with permission checks bypassed |
--agy-command |
unset | Explicit AGY executable override |
--agy-dangerously-skip-permissions |
off | Start AGY with tool permission requests auto-approved |
--no-agent-auto-detect |
off | Disable discovery of optional agent CLIs |
--shell |
powershell |
Windows wrapper (powershell or cmd); ignored on Unix |
--token |
generated | Explicit authentication token |
--no-open-browser |
off | Prevent automatic browser launch |
--log-level |
info |
Rust tracing filter |
--update-policy |
notify |
Official application update checks: notify or off; installation still requires confirmation |
Equivalent environment variables:
CODEX_WEB_HOST
CODEX_WEB_PORT
CODEX_WEB_MAX_SESSIONS
CODEX_WEB_PROJECT_DIR
CODEX_WEB_STATE_DIR
CODEX_WEB_COMMAND
CODEX_WEB_PRIMARY_AGENT
CODEX_WEB_NEW_SESSION_COMMAND
CODEX_WEB_CODEX_COMMAND
CODEX_WEB_CLAUDE_COMMAND
CODEX_WEB_CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS
CODEX_WEB_AGY_COMMAND
CODEX_WEB_AGY_DANGEROUSLY_SKIP_PERMISSIONS
CODEX_WEB_NO_AGENT_AUTO_DETECT
CODEX_WEB_SHELL
CODEX_WEB_TOKEN
CODEX_WEB_LOG_LEVEL
CODEX_WEB_UPDATE_POLICY
Command-line arguments override environment variables.
scripts/run.ps1 exposes the same setting as -MaxSessions; when that
parameter is omitted it leaves CODEX_WEB_MAX_SESSIONS available to the
server. run.sh forwards --max-sessions after its project argument. Raising
the capacity increases both process and memory exposure: every running slot
may own a full agent CLI process, and every managed entry may retain a 16 MiB
output buffer.
The command values are executable names or paths, not shell expressions. Do not pass pipes, redirections, command substitutions, or chained commands.
If the primary terminal is launched by a trusted wrapper that resumes one
specific Codex thread, also set --new-session-command codex. The primary
terminal will use the resume wrapper, while the New button will start an
independent Codex CLI process when the primary Codex card is selected. Other
agent cards use their own override or default command.
By default, the server probes the executable implied by --primary-agent and
auto-detects codex, claude, and agy from PATH and their documented
per-user locations. It runs a fixed --version probe with bounded output and a
timeout. A ready optional agent is offered in New. Missing and
misconfigured agents remain visible in the catalog with manual installation
or repair guidance, but cannot create sessions.
An explicit --command, --codex-command, --claude-command, or
--agy-command is authoritative. If it does not resolve or its --version
probe fails, the profile is misconfigured; the server does not silently fall
back to another binary. --no-agent-auto-detect (or
CODEX_WEB_NO_AGENT_AUTO_DETECT=true) restricts optional profiles to explicit
configuration. The primary profile is still validated.
The browser cannot provide executable paths, arguments, URLs, or shell syntax.
For a service whose PATH differs from an interactive shell, configure
trusted absolute paths:
.\scripts\run.ps1 `
-Project "C:\Projects\my-app" `
-CodexCommand "$env:APPDATA\npm\codex.cmd" `
-ClaudeCommand "$HOME\.local\bin\claude.exe" `
-AgyCommand "$env:LOCALAPPDATA\agy\bin\agy.exe"Each managed terminal is a fresh agent-session boundary. The launcher removes
only the inherited nesting markers CODEX_THREAD_ID and CLAUDECODE before
version checks and PTY startup; authentication and provider environment
variables remain untouched.
Every Codex profile receives one fixed --yolo process argument. This applies
to the primary terminal, New, restarts, and dedicated @cwt reviewers,
including trusted executable overrides. It disables Codex approval prompts and
sandboxing. The bounded discovery probe remains exactly codex --version.
There is currently no server or browser opt-out, so custom Codex wrappers must
accept or forward --yolo.
To deliberately auto-approve every tool action for both optional profiles:
.\scripts\run.ps1 `
-Project "C:\Projects\my-app" `
-ClaudeCommand "$HOME\.local\bin\claude.exe" `
-ClaudeDangerouslySkipPermissions `
-AgyCommand "$env:LOCALAPPDATA\agy\bin\agy.exe" `
-AgyDangerouslySkipPermissionsThe equivalent direct launches are
claude --dangerously-skip-permissions and
agy --dangerously-skip-permissions. These modes remove the normal approval
barrier for file changes, commands, network access, and other supported tools.
Use them only when the operating-system account, every selectable working
directory, network, credentials, and reachable services are intentionally
placed inside the agent's trust boundary. The switches are off by default.
For a command such as codex, claude, or agy, Windows searches PATH and
the documented per-user locations in this extension preference:
<command>.exe<command>.cmd- an exact extension already supplied by the caller
.cmd entry points are invoked through cmd.exe /d /s /c call.
Executable entry points normally use PowerShell unless --shell cmd is
selected. .ps1 shims are intentionally not selected automatically because a
PowerShell execution policy can block npm-generated .ps1 shims.
Unix searches the configured PATH for the exact executable name and checks
the documented per-user locations during auto-detection. A trusted absolute
path can also be supplied:
--command /usr/bin/codexAfter the selected CLI's --version succeeds, the resolved executable is
started directly inside the Unix PTY without a shell wrapper.
Install and connect Tailscale on both the server and browser devices:
tailscale status
tailscale ip -4The safest direct bind is the server's specific Tailscale IP:
export CODEX_WEB_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
./dist-linux/codex-web \
--project "/home/user/projects/my-app" \
--host "100.x.y.z" \
--port 8787 \
--no-open-browserOn Windows, use the equivalent -ListenHost option:
.\scripts\run.ps1 `
-Project "C:\Projects\my-app" `
-ListenHost "100.x.y.z" `
-Port 8787 `
-NoOpenBrowserrun.ps1 and the server inherit CODEX_WEB_TOKEN; omitting -Token keeps the
credential out of the process command line.
Open:
http://100.x.y.z:8787/?token=YOUR_TOKEN
Binding to 0.0.0.0 also works, but exposes the port on every IPv4 interface.
Use it only when firewall rules and the surrounding network are understood:
./dist-linux/codex-web \
--project "/home/user/projects/my-app" \
--host 0.0.0.0 \
--port 8787 \
--no-open-browserTailscale provides private transport, but the application URL is still HTTP unless an HTTPS reverse proxy is added. Restrict access with Tailscale ACLs and never add a public router port-forward.
A backend bound to 127.0.0.1 accepts only loopback browser origins. Therefore,
an HTTPS reverse proxy serving a non-loopback hostname must use a backend bound
to either:
- the exact private or Tailscale address reachable by the proxy; or
0.0.0.0, protected by a host firewall that admits only the intended interface and proxy.
Preserve the browser-facing Host header and port. Forward WebSocket Upgrade
and Connection headers and binary frames without conversion. Otherwise the
strict WebSocket Origin check rejects the connection.
The current token is server-wide, not per-user or per-session. Anyone who receives it has the same ability as the owner to list, view, type into, create, restart, terminate, and remove eligible managed sessions. It also authorizes filesystem-root discovery, directory browsing, manual absolute-path resolution, Favorites/Recent changes, and launching agents anywhere readable by the server account. The opaque directory IDs used by the API are transport values, not additional access control.
Multiple browsers attached to one session share both input and PTY dimensions. The latest valid resize wins, so desktop and mobile clients with different viewport sizes can trigger redraw or scroll changes for each other.
Share the authenticated URL only when full read/write terminal access is intended. Read-only links, expiring share links, per-session grants, and revocation without rotating the server token are future work described in TODO.md.
The dot in the header's identity area combines the HTTP session lifecycle and the browser WebSocket state: green means attached and healthy, amber pulses while connecting or reconnecting, and red means disconnected, authentication failed, or an exited agent. The same state is exposed as visually hidden text for assistive technology, and connection transitions are announced through a polite live region. Reconnect attempts use increasing delays and do not restart the selected agent.
The identity area also shows the active project's full server path (it may visually ellipsize on narrow screens; the tooltip and DOM text keep the complete value) and the selected session's agent label. It follows the active tab. There is no manual reconnect button; reattachment is automatic.
The header shows one tab for each server-managed terminal. The active tab is highlighted and each tab includes a lifecycle-status dot.
-
Select a tab to attach the single browser terminal view to that managed PTY.
-
On desktop, hover a tab to see only that terminal's complete project path; its lifecycle status remains available to assistive technology.
-
On mobile, the header starts with the project/Menu row collapsed. Its status-and-arrow control expands or collapses that row, retains a 44 px touch target, and remembers the choice in that browser. The tabs and @cwt remain visible in either state.
-
Swipe the tab strip horizontally on mobile.
-
On desktop the tabs spread across the full remaining header width; the left/right arrows appear (overlaid on the strip's edges) only when the tabs genuinely overflow, and a wheel or trackpad scrolls the strip as well.
-
Desktop reads left to right: the identity area (project path, agent, status dot), then the ellipsis Menu button (
…, labelled "Menu"), then the left-aligned tabs and @cwt. Expanded mobile puts Menu and the collapse arrow first, then the identity while preserving the rightmost project name when the full path ellipsizes. The collapsed mobile row keeps a compact status/disclosure followed by the tabs and @cwt. General actions live in the Menu; preferences live in Settings, which the Menu opens. -
New terminal in the Menu creates another terminal.
-
Manage sessions in the Menu opens the detailed session list.
-
Attach in the detailed list switches to that managed PTY.
-
Attaching does not stop the previously displayed session.
-
Refresh reloads sanitized session metadata.
-
Remove terminates and deletes a non-primary managed session.
-
The primary
<agent> 1entry (for exampleCodex 1) cannot be removed.
@cwt opens a supervised cross-agent composer without changing xterm input handling. A new peer thread always starts a fresh dedicated reviewer in the selected, revalidated server directory. The picker defaults to the source terminal's current directory and can switch to another server folder through Favorites, Recent, Browse, or an absolute path. It does not reuse or modify a normal tab, even when a matching agent appears idle.
The operational sequence is:
- choose the reviewer agent, working folder, and action, then use Prepare summary while the source is at an empty agent prompt;
- wait for the source agent to submit a bounded handoff;
- inspect or edit Preview summary;
- use Send to reviewer while the reviewer is at an empty agent prompt;
- wait for Response ready;
- use Return to source while the source is at an empty agent prompt;
- use a follow-up or Recheck to retain that same reviewer context.
Concrete example: from a Codex source tab, choose Verify with Claude and
use Change folder when Claude should inspect a different project. Enter
Review the current implementation for correctness, security regressions, and missing Windows/Linux tests. Inspect the generated handoff before dispatch.
The dedicated reviewer starts in the selected directory without changing any
ordinary tab. Return Claude's response to the same Codex source, then use
Recheck for another pass that retains Claude's reviewer context and
working directory. Use + New reviewer only when a clean reviewer
conversation is intentional.
It is disabled when session capacity is full, while follow-ups on an existing
reviewer remain available. One reviewer thread retains at most 32 turns; close
it and start a clean peer after reaching that boundary. The broker permits at
most 256 active in-memory peer threads, though the configured session capacity
normally applies first because each thread owns a dedicated terminal.
Catalog Ready means executable discovery and the bounded --version probe
succeeded. A fresh provider TUI may still require sign-in, onboarding, or
folder trust; complete those prompts manually in the linked reviewer tab.
The reviewer is visible as a linked tab and counts toward the configured
session capacity. × closes and removes it only after the reviewer process exit is
confirmed. A termination error keeps the exact reviewer and thread available
for a retry instead of reporting a false successful close. Provisioning owns
the reviewer identity before its PTY starts, so a concurrent Close is rejected
until that transaction finishes. The server never closes another session to
make room. An open peer thread prevents restart, terminate, or deletion of its
source. Generic restart/terminate controls are disabled or rejected for the
reviewer itself because a restarted PTY would not retain the claimed context.
Peer coordination does not parse raw PTY output and does not infer agent
idleness from silence, cursor position, or process state. The agents call a
hidden helper in the same codex-web executable. That helper connects to an
ephemeral loopback-only listener with a random capability scoped to the exact
PTY generation. The capability is not the browser token, is never accepted on
the public listener, and is revoked when the generation ends.
CODEX_WEB_TOKEN is stripped from managed PTYs and version probes. Server
shutdown disables new capabilities and revokes existing ones before releasing
the private listener; an unexpected private-listener exit also stops the
public service.
Prepare summary, Send to reviewer, and Return to source are an
explicit operator acknowledgement that the named terminal is at an empty
prompt, not an inferred state. The corresponding API requests require
sourceReady: true
or reviewerReady: true, and delivery is rejected if the PTY generation has
changed. Do not confirm readiness while the CLI is showing a permission,
login, trust, or first-run prompt, or while text is partially entered.
If an agent's tool policy declines or blocks the helper invocation, the turn remains visibly pending. Inspect that agent's tab, approve the local command if appropriate, or close the peer thread. Do not diagnose completion by copying terminal output into the application. Handoffs and responses are limited to 64 KiB, live only in memory, and are lost when the server stops. Their line endings are normalized and unsafe terminal control characters are rejected by both the broker and helper.
New terminal in the header Menu uses two focused steps. Dismissing the folder picker returns focus to the Menu button.
- Choose a project folder selects the native working directory on the server.
- New terminal selects Codex, Claude, or AGY and starts that CLI in the chosen directory.
The folder dialog has:
- Favorites — explicitly starred server folders, up to 100;
- Recent — up to 30 successfully used folders, newest first and deduplicated;
- Browse — filesystem roots, breadcrumbs, Up, and one level of sorted child directories at a time;
- Folder path — a manual absolute path on the server, useful when a folder has more than the 10,000 displayed subdirectory limit.
Files never appear in the browser and directory listing is not recursive. Paths refer to the host running Codex Web Terminal, not the viewing phone or laptop. Use folder advances to the agent picker. Change folder returns without losing the intended launch flow. The star action adds or removes a Favorite.
A Recent entry remembers its last agent. A Favorite remembers its preferred agent after a successful launch from that directory. Those entries provide a direct Start Codex, Start Claude, or Start AGY action. The server still revalidates the folder and the frontend checks the current catalog. If the remembered agent is no longer ready, the full agent picker opens so another installed agent can be selected.
The agent dialog identifies the server operating system and architecture and makes clear that the CLI runs on the server host, not in the viewing browser or phone. It also displays the chosen working folder.
Each agent card reports:
- Ready and
Installed version …when the fixed version probe succeeds; - Not found when no candidate executable resolves;
- Configuration error when an explicit override or resolved executable fails validation.
Only a ready card provides Start Codex, Start Claude, or Start AGY.
A missing or misconfigured card shows a selectable provider command, Copy,
Official docs, the required shell, the --version verification command,
and Check again. Opening New terminal always requests a fresh catalog
so a tab cannot keep stale availability from an earlier server generation. The
dialog's Refresh and per-card Check again make the same
/api/agent-catalog?refresh=true request; it never executes the displayed
command. Creation errors remain in the open dialog. A successful create closes
it and attaches the terminal.
On a phone, scroll vertically inside the agent-card list. Each card keeps its own Start action; later cards and their buttons remain reachable without scrolling the underlying terminal page.
The server allows 20 managed sessions by default and accepts a configured
capacity from 1 through 256. The Menu's Manage sessions entry displays the
current/capacity value, for example 3/20. The count includes the primary
entry, ordinary running or stopped entries, and dedicated peer reviewers.
New terminal is disabled when the capacity is full; existing peer
follow-ups remain available because they reuse their reviewer. A slot is
released only by deleting a removable ordinary entry or closing its peer
thread. Each entry has its own lifecycle, output replay buffer, and
connected-client count; each running entry also owns a full agent process.
The Codex card always warns that approvals are disabled because the server
launches Codex with --yolo. Claude and AGY cards show the same warning when
their dangerous-mode switch is active. Such agents may edit files and run
commands without asking for confirmation.
There is no manual reconnect control. After a network interruption the browser recreates only its WebSocket attachment automatically, with increasing delays; reloading the page forces a fresh attachment immediately. Neither touches the underlying agent process.
Restart agent in Settings terminates and recreates the selected agent's
PTY. Its stable
terminalId remains, but its sessionId, PID, and PTY generation change.
The agent profile and selected working folder remain the same, and a
successful restart refreshes that folder in Recent. Output from the previous
generation is not treated as current live output.
On Linux, termination targets the direct PTY child and cannot guarantee cleanup
of a descendant that deliberately detached itself.
Full screen in the header Menu requests browser fullscreen mode. Leaving fullscreen does not affect the server session.
The ellipsis button (…) beside the status dot is labelled Menu on hover
and for assistive technology. It opens a compact action menu holding
New terminal, Settings, Manage sessions, and Full screen —
general application actions rather than preferences. The menu supports mouse,
touch, and keyboard (arrow keys move, Home/End jump, Escape closes and
returns focus to the button, clicking outside dismisses), stays inside the
viewport on narrow screens, and carries the update badge when a newer release
is available.
On desktop, pressing an unmodified / while a non-editable header control has
focus sends / to the connected terminal. This prevents Firefox Quick Find
from taking over after using header controls such as the tabs or the Menu
button. Slash is not routed while the menu popover is open. Slash remains
normal text in form fields and dialogs, and modified shortcuts such as
Ctrl+/ are left to the browser or operating system.
The Show mobile keys setting shows or hides the mobile toolbar. Its order begins with Enter and the arrow keys, followed by Esc, Ctrl+C, Tab, Ctrl mode, Page Up/Down, Ctrl+L, Top, Live, and Hide, so the interrupt keys stay inside the first screenful on a narrow phone.
- PgUp/PgDn sends the standard terminal Page Up/Page Down input sequences to the selected PTY, including full-screen alternate-buffer TUIs.
- Top moves to the oldest retained client-side line.
- Live returns to current terminal output.
- Ctrl applies Ctrl to the next typed ASCII letter and then turns off.
- Hide hides the toolbar; it can be shown again from Settings.
On a touch/coarse-pointer device, xterm keeps its established mobile terminal width and a 28 px right-edge drag target. The narrow thumb appears when that target is touched, remains visible throughout the drag, and hides after 20 seconds without scrollbar interaction. You can still swipe anywhere inside the terminal to move through scrollback.
On Android, replacement/autocorrect events are converted to terminal Backspace-plus-suffix input. Diagnostics count these translations but never include the original or replacement text.
Settings opens from the header Menu and now contains only preferences and maintenance: font size, client scrollback, theme, cursor blinking, the mobile toolbar, software updates, diagnostics, Restart server, Restart agent, Terminate agent, and Forget token. Copy diagnostics captures mobile viewport measurements after terminal focus; it does not include the authentication token, keyboard input, or terminal text.
Restart server is a real orderly backend restart, not a page refresh. It
ends every PTY and in-memory @cwt thread. The initiating browser tab then
recreates ordinary non-primary tabs, in order, using the same allowlisted
agent and opaque directory ID. Each is a fresh PTY with new terminal/session
identity; old output and conversational context are not resumed. Dedicated
reviewer tabs are excluded. If a non-empty plan cannot be saved in that tab's
sessionStorage, the restart is refused.
The button is enabled only when the direct server or stable root supports the same-version restart protocol. A worker installed by the built-in updater under an older root stays running and shows why the button is disabled. Install the complete new release as the stable launcher to enable it; do not overwrite a running package in place.
Software updates shows the running server version and the newest official
stable release. Check for updates performs a read-only request to the fixed
GitHub repository. For a marked official package, Update to X.Y.Z and restart
downloads and validates the complete native archive side-by-side. It is
enabled only after explicitly confirming that all PTYs and @cwt reviewer
threads will end. The initiating browser tab applies the same bounded ordinary
tab restoration used by Restart server. Favorites and Recent remain in the
state directory.
Source/development builds show the release but cannot self-install it. Update them with the reviewed source/build procedure. The browser never supplies a download URL, archive path, checksum, repository, command, or executable name to the server.
Terminate agent stops the selected process without deleting its managed entry. Forget token removes the token from the current browser tab.
Closing the browser or losing connectivity does not terminate managed PTYs. The server retains up to 16 MiB of raw PTY output per session. A newly attached browser receives at most the newest 2 MiB and then:
- receives the current sanitized session snapshot;
- resets the xterm screen;
- receives a bounded replay;
- transitions to live output without a replay/live gap.
xterm also keeps client-side scrollback, 10,000 lines by default. The server buffer contains raw ANSI bytes rather than a rendered screen model. If the oldest ANSI state has been discarded, a very old replay can look imperfect; causing the selected agent to redraw or restarting that session repairs it.
All HTTP API requests require a bearer token:
curl \
-H "Authorization: Bearer $CODEX_WEB_TOKEN" \
http://127.0.0.1:8787/api/healthHealthy output has:
{
"status": "ok",
"serverVersion": "0.3.7",
"serverRestartSupported": true,
"codexInstalled": true,
"sessionRunning": true,
"connectedClients": 0,
"sessionCount": 1,
"runningSessions": 1,
"maxSessions": 20
}Important distinction:
codexInstalled: truemeans the most recent preflight for at least one registered session successfully resolved the command and ran the configured agent's--version; it is not a continuously refreshed installation probe. The field name is retained for API compatibility.sessionRunning: truemeans at least one PTY process is actually running.maxSessionsis the configured registry capacity, not a hard-coded UI value.sessionCountincludes stopped entries and dedicated reviewers until they are removed or closed.serverVersionis the native backend version. The update UI accepts the new process only when this equals the staged release version.serverRestartSupportedisfalsewhen a built-in update is running under an older stable launcher that cannot safely interpret same-version restart.
The frontend can still load while a PTY is failed so diagnostics and restart controls remain available.
Inspect the complete agent catalog:
curl \
-H "Authorization: Bearer $CODEX_WEB_TOKEN" \
"http://127.0.0.1:8787/api/agent-catalog?refresh=true"Schema version 1 includes server.os, server.arch, server.shell, and one
entry per cataloged agent. All three agents are cataloged by default; optional
agents without explicit overrides are omitted when auto-detection is disabled:
{
"kind": "claude",
"state": "ready",
"configuration": "auto",
"version": "2.x",
"dangerouslySkipPermissions": false,
"install": {
"command": "platform-specific manual command",
"shell": "powershell",
"verifyCommand": "claude --version",
"updateCommand": "claude update",
"docsUrl": "https://code.claude.com/docs/en/setup",
"requiresServerAccess": true
}
}version is null when the CLI is not ready. missing means no supported
candidate resolved. misconfigured means an explicit override or candidate
was found but failed validation. configuration: "override" means the
command value is authoritative and never falls back to another executable.
Repairing that exact file or its permissions can be followed immediately by
Check again. Changing or removing the startup override value requires
updating the server configuration and restarting the server; running a generic
installer alone does not replace an override. The legacy /api/agents
endpoint lists only ready kinds and exists for older frontends.
List sessions:
curl \
-H "Authorization: Bearer $CODEX_WEB_TOKEN" \
http://127.0.0.1:8787/api/sessionsList active peer threads:
curl \
-H "Authorization: Bearer $CODEX_WEB_TOKEN" \
http://127.0.0.1:8787/api/peer/threadsPeer API responses contain the current handoff or reviewer response after one
has been submitted. Treat them as terminal-conversation data: do not put them
in diagnostics or logs. Browser routes use the normal bearer token. The
private /internal/v1/peer helper route exists only on a separate loopback
listener and is not served on the configured public port.
Inspect filesystem roots and saved workspace state:
curl -H "Authorization: Bearer $CODEX_WEB_TOKEN" \
http://127.0.0.1:8787/api/filesystem/roots
curl -H "Authorization: Bearer $CODEX_WEB_TOKEN" \
http://127.0.0.1:8787/api/workspacesList the configured default directory with an empty object, or return a
previously received opaque directoryId:
curl -X POST \
-H "Authorization: Bearer $CODEX_WEB_TOKEN" \
-H "Content-Type: application/json" \
-d '{}' \
http://127.0.0.1:8787/api/filesystem/listThese responses contain server filesystem paths. Sanitize them before sharing
diagnostics. All /api/filesystem/* and /api/workspaces* routes require the
same bearer token as session control. Session-create, directory list/resolve,
and Favorite-upsert JSON bodies are capped at 256 KiB.
Do not paste production health commands containing real tokens into tickets, chat messages, or shared logs.
Structured tracing output includes:
- bind address and project directory;
- PTY startup and PID when available;
- client connect/disconnect;
- restart and process exit;
- sanitized errors.
It deliberately excludes tokens, keyboard input, terminal output, Codex
credentials, and authentication files. The separate startup println!
intentionally prints the complete authenticated URL. If stdout is redirected
to a file or journal, that destination contains the token.
Select verbosity:
--log-level info
--log-level codex_web_terminal=debugDebug logging remains server-level; it does not enable terminal-content logging.
For a persistent single-user installation, place the package in a stable directory such as:
/home/alice/apps/codex-web/
├── codex-web
└── web/
Create a protected environment file:
install -d -m 0700 "$HOME/.config/codex-web"
umask 077
python3 -c 'import secrets; print("CODEX_WEB_TOKEN=" + secrets.token_urlsafe(32))' \
> "$HOME/.config/codex-web/environment"
chmod 0600 "$HOME/.config/codex-web/environment"Create ~/.config/systemd/user/codex-web.service:
[Unit]
Description=Codex Web Terminal
After=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/projects/my-app
EnvironmentFile=%h/.config/codex-web/environment
ExecStart=%h/apps/codex-web/codex-web --host 127.0.0.1 --port 8787 --project %h/projects/my-app --state-dir %h/.local/state/codex-web-terminal --command /absolute/path/to/codex --no-open-browser
Restart=on-failure
RestartSec=3
KillSignal=SIGINT
KillMode=control-group
StandardOutput=null
StandardError=journal
[Install]
WantedBy=default.targetLoad and start it:
systemctl --user daemon-reload
systemctl --user enable --now codex-web.service
systemctl --user status codex-web.serviceReplace /absolute/path/to/codex with the result of command -v codex for the
service user. Using an absolute path avoids differences between an interactive
shell's PATH and the systemd user-manager environment. The explicit
--state-dir keeps Favorites and Recent in a predictable per-user location;
the server creates a missing final directory with mode 0700. If it already
exists, it must be owned by the service user and grant no group/other
permissions.
ExecStart must continue to point at the complete manually installed v0.2
bootstrap package, not at a worker below
<state-dir>/updates/releases. Do not delete that package after a built-in
update. Its process remains systemd's stable MainPID; after the first update
it supervises the active worker and every later generation without creating a
nested supervisor chain.
Keep Restart=on-failure, KillSignal=SIGINT, and
KillMode=control-group. A normal update is handled inside the stable root and
does not require systemd to replace MainPID. An unrecoverable supervisor or
worker/rollback failure makes the root fail so Restart=on-failure can start
the bootstrap again. A service stop sends SIGINT to the root, which first asks
its current worker to stop; KillMode=control-group remains the final
service-wide containment boundary.
The Settings Restart server action also keeps MainPID stable after the
bootstrap is supervising a worker. The worker performs orderly shutdown with
the dedicated same-version restart status, and the root starts the exact
current executable only after the normal authenticated readiness exchange.
This hardened example discards stdout because startup stdout contains the authenticated URL. It also discards the normal structured tracing stream, which currently uses stdout. Service state is still available with:
systemctl --user status codex-web.serviceFor detailed diagnostics, stop the unit and run the same command interactively
in a private terminal. If StandardOutput=journal is enabled temporarily,
understand that the journal will retain the complete token-bearing startup URL
until it is rotated or vacuumed.
Stop:
systemctl --user stop codex-web.serviceTo reach this service over Tailscale, replace 127.0.0.1 in ExecStart with
the machine's specific Tailscale IP, then run daemon-reload and restart the
unit. Avoid 0.0.0.0 unless all interfaces are intentionally in scope.
For an interactive server, press Ctrl+C in the server console. The server stops its managed sessions and closes the HTTP listener.
For a user service:
systemctl --user stop codex-web.serviceAfter shutdown, verify that the port is no longer listening:
ss -ltnOn Windows:
Get-NetTCPConnection -State Listen -LocalPort 8787 -ErrorAction SilentlyContinueDo not kill unrelated processes merely because they use a similar name. Resolve the exact service, PID, and port first.
Server restarts terminate all managed PTYs. Save or finish important agent
work before upgrading. A restart initiated by the current Settings page can
recreate ordinary tab definitions as fresh PTYs, but it cannot preserve live
processes, output, agent context, or @cwt reviewer threads.
The v0.1-to-v0.2 transition must be installed manually from the complete v0.2
archive. Stop the old service, extract the executable and adjacent web,
documentation, and license directories together, point ExecStart at that
v0.2 bootstrap package, and verify it before removing the old v0.1 package.
After that:
- Open Settings → Software updates and choose Check for updates.
- Read the release notes. Automatic installation remains disabled unless the release is stable and immutable and its exact platform asset exposes a GitHub SHA-256 digest.
- Finish important terminal work and confirm that every PTY and
@cwtthread may be terminated. - Choose Update to X.Y.Z and restart. The server streams the archive into
<state-dir>/updates, checks the GitHub digest andSHA256SUMS.txt, safely extracts and validates the full package, persists only the update request ID and source/target versions inpending.json, releases the update lock, and then performs an orderly shutdown. - The original v0.2 root process remains alive with the same PID. It validates the matching pending transition, then starts the staged package as a worker with the same effective project, host, port, state directory, agent configuration, token, and update policy. The token is passed only through the worker environment and is consumed/removed there before application threads start. The root also supplies a fresh per-launch readiness nonce through the private worker environment; the worker consumes/removes that variable before serving.
- The root accepts the candidate only after direct authenticated local health,
with system proxies disabled, reports the expected
serverVersionand returns that exact nonce. It then atomically commitsactive.jsonwith the active and exact previous versions, clears the matching pending record, and continues supervising that worker. The browser reloads only after the same version check and a new primary terminal identity, then recreates the initiating tab's saved ordinary sessions with the same agents and directories. - If package validation, startup, readiness, or active-pointer commit fails,
the candidate is terminated and waited for,
active.jsonremains on the previous version, and the root starts the exact prior executable and requires its readiness. Inspect the server console before retrying.
If the root itself restarts during a transition, it resumes only a strict
pending source-to-target transition that matches the known active generation.
A pending record whose target was already committed is cleared; malformed or
stale pending state is quarantined. Do not edit active.json, pending.json,
or versioned worker directories manually.
The updater never requests administrator/root elevation and never overwrites the executable that launched it. The manually installed v0.2 bootstrap remains the long-lived trust anchor; workers live side-by-side, with the active release and at most one prior managed release retained for rollback. Unknown files in the bootstrap package directory are not touched. Ensure the state filesystem has room for the compressed archive and extracted package.
Never delete the bootstrap package while an interactive launcher, scheduled task, or service still starts it. Ordinary worker updates do not update the root supervisor itself. If release notes identify a supervisor protocol or security change, perform another manual full-archive replacement and update the configured launch path before resuming built-in updates.
Disable checks with --update-policy off or
CODEX_WEB_UPDATE_POLICY=off. A failed/offline check does not stop the server,
PTYs, or browser.
Recommended sequence:
- For an official release, download the new archive and verify its checksum and attestation before extraction. For a source deployment, pull or check out the desired reviewed commit and run the full Windows and Linux build/test matrix from BUILDING.md.
- Read the release notes and confirm that the host satisfies the artifact's platform and glibc requirements.
- Keep the old package until the new one has passed validation.
- Stop the existing server.
- Replace the executable, the entire adjacent
webdirectory, and the accompanying documentation/license bundle together. - Start the new server with the same project, state directory, and explicit token if continuity of Favorites/Recent, the browser URL, and the credential is required. This does not preserve PTY processes or live terminal sessions.
- Check
/api/health, load the frontend, attach, and verify keyboard input. - Remove a superseded manual package only after the configured launcher or
service points at the new verified bootstrap. Never remove the bootstrap
package that
ExecStartcurrently names.
Do not copy a managed worker out of the state directory and make it the service executable. A manual bootstrap replacement uses a complete verified archive and stops the existing root/worker process tree first.
Do not mix a new frontend with an older backend during deployment. Before deployment, confirm that the Markdown documentation still matches the current commands, versions, package contents, supported platforms, behavior, and known limitations.
Check the session snapshot and server log. The HTTP server intentionally
remains available after PTY startup fails. Confirm the selected agent's
--version command as the same OS user, inspect /api/agent-catalog, and
check sessionRunning.
Use the New terminal card's shell, command, and verification instructions
on the server host. Not found means no supported executable resolved.
Configuration error commonly means an explicit path is wrong, the file is
not executable, or --version failed. Explicit overrides never fall back.
After correcting the host installation, select Refresh or Check again. If startup flags or service environment variables changed, restart the server first; understand that restarting destroys its in-memory PTYs. Do not paste the displayed install command into the browser developer console.
Use the URL from the current server instance. A generated token changes after restart. Five repeated failures from one address trigger a temporary one-minute block.
Confirm that the path is absolute on the server host, still exists, is a
directory, and can be listed by the exact operating-system account running
codex-web. A missing directory returns 404; insufficient access returns
403. A path copied from the viewing phone or laptop is not meaningful unless
that same native path exists on the server. --project does not restrict the
picker, and changing it does not repair permissions on another directory.
Look in the configured state directory for
workspaces.corrupt.<uuid>.json. This means workspaces.json exceeded
32 MiB, was invalid, or used an unsupported schema version. The server
preserves the file and loads clean state; successful primary startup may then
add the default folder to Recent. Stop the server before restoring a reviewed
schema-1 backup.
If startup instead reports an unsafe state location, nothing is quarantined or
chmod-repaired. Use a dedicated non-link directory. On Unix, make it owned by
the effective service user with no group/other permissions; 0700 for the
directory and 0600 for an existing state file are the normal settings. Also
verify create/rename permission for the service account.
Inspect a Unix default without following or changing anything:
case "${XDG_STATE_HOME:-}" in
/*) workspace_state_dir="$XDG_STATE_HOME/codex-web-terminal" ;;
*) workspace_state_dir="$HOME/.local/state/codex-web-terminal" ;;
esac
if test -e "$workspace_state_dir" || test -L "$workspace_state_dir"; then
test ! -L "$workspace_state_dir"
stat -c '%F %U %G %a %n' -- "$workspace_state_dir"
if test -e "$workspace_state_dir/workspaces.json" ||
test -L "$workspace_state_dir/workspaces.json"; then
test ! -L "$workspace_state_dir/workspaces.json"
stat -c '%F %U %G %a %n' -- \
"$workspace_state_dir/workspaces.json"
fi
fiAfter verifying the exact owner and path, the owner can tighten overly broad Unix modes explicitly:
chmod 0700 -- "$workspace_state_dir"
if test -e "$workspace_state_dir/workspaces.json"; then
chmod 0600 -- "$workspace_state_dir/workspaces.json"
fiDo not point --state-dir directly at /, the home directory,
XDG_STATE_HOME, the current directory, or the system temporary directory;
use a dedicated child directory. Correct wrong ownership deliberately as an
administrator rather than making the application take ownership.
On Windows, inspect reparse metadata and the inherited ACL before restarting:
$workspaceStateDir = Join-Path $env:LOCALAPPDATA "codex-web-terminal"
Get-Item -LiteralPath $workspaceStateDir -Force |
Format-List FullName,Attributes,LinkType,Target
Get-Acl -LiteralPath $workspaceStateDir | Format-ListUse a dedicated child directory, not a drive root, profile/base directory, current directory, or temporary directory. Remove unexpected reparse points or repair ACLs through normal Windows administration; the application does not rewrite them.
Confirm:
- the server process and port are active;
- the URL host, port, and scheme are correct;
- a reverse proxy forwards WebSocket Upgrade and binary frames;
- the page Origin host matches the public Host;
- browser extensions are not blocking WebSockets.
Check both devices:
tailscale status
tailscale ping SERVER_NAMEVerify that the server bound either the exact Tailscale IP or an intentionally broader address. Check Tailscale ACLs and the host firewall.
Check:
command -v codex
codex --version
command -v claude
claude --version
command -v agy
agy --version
test -r /dev/ptmxCheck only the CLIs you intend to use. Also verify executable permission on the catalog's resolved command and read access to the default or selected working directory.
Use where.exe codex, where.exe claude, or where.exe agy. Prefer a
discovered .exe or .cmd entry point. The application deliberately avoids
automatic .ps1 selection.
Use Settings → Mobile viewport diagnostics, tap the terminal, wait for collection to finish, and copy the diagnostic JSON. Record the browser, device, orientation, and exact interaction sequence. Diagnostics intentionally exclude terminal content and credentials.
Before exposing the service to another device:
- The primary CLI is ready and authenticated for the service user.
- Optional agent status/version and manual commands match the server OS.
- Dangerous permission-bypass flags are off unless explicitly required.
- The default project directory is correct.
- Everyone holding the token is trusted to browse and launch in every directory readable by the server account.
- The workspace state directory is private, writable by the service account, and included in the intended backup policy.
- Every server instance that can run concurrently has a distinct state directory.
- The build and tests passed on the target platform.
- The package contains the matching executable and
webassets. - The token is strong, private, and not committed.
- The bind address is loopback or a private/Tailscale interface.
- Firewall and Tailscale ACL scope is understood.
-
/api/healthreports a running session. - The browser can browse a disposable folder, start the selected agent there, attach, type, reconnect, and replay.
- A precise stop procedure is known.