The PTY runtime exported an empty NODE_CHANNEL_FD to override the daemon's
IPC descriptor, because bun-pty merges the native environ back into the
child and a JS-only delete does not stick. Node CLIs launched from the
shell (opencode, claude) then printed "warn: Failed to parse IPC channel
number ''" on exit.
The Linux-only env -u ARGV0 wrapper now applies on macOS and Linux and
also unsets NODE_CHANNEL_FD, so the variable is gone instead of empty.
Testing: runtime and inherited-env tests updated for the POSIX wrapper;
verified in the running app that exiting opencode no longer prints the
warning.
Opening the terminal panel sometimes showed stray fragments on the prompt
row: zsh's end-of-line mark and pieces of the prompt path. The shell had
laid its output out for one PTY width, but the client replayed that
history into an emulator of another width (an early size estimate, a
remount, or a renderer rebuild after fonts loaded). ghostty-web's reflow
then left fragments the shell's SIGWINCH redraw never clears.
The server now reports the PTY cols/rows in every snapshot, the transport
carries them through projections and accepted resizes, and the viewport
replays a sized snapshot chunk at that size before returning to the
fitted size. The container-based size estimate only seeds newly spawned
shells and is no longer sent to a running PTY.
Tests cover the sized replay, the store chunk size, the transport
projection, and the server snapshot; verified in a production build by
reloading with the panel open and switching tabs at a changed width.
* fix(sessions): keep a shared chat directory until its last session is deleted
Deleting a root chat session removed its managed scratch directory even
when forks, side threads, or subagents still lived in it; OpenCode then
failed every prompt in those sessions with FileSystem.realPath NotFound.
The directory is now removed only once no other known session resolves
to it. The deleted subtree does not count, because the server cascade-
deletes it, and an unloaded global cache keeps the directory instead of
guessing.
Closes#3312.
* fix(sessions): relocate a session whose worktree directory disappeared
A worktree removed outside OpenChamber, by the agent or by hand, left its
sessions pointed at a path that no longer exists: every terminal create
and restart failed with "Invalid working directory" and the tab stayed
stuck, while Git, Files, and prompts kept targeting the dead path.
The terminal server now names that one rejection (TERMINAL_CWD_MISSING)
instead of substituting a directory of its own. The shared UI reuses the
archived-restore fallback for live sessions: a server-confirmed missing
directory moves the session and its stranded subtree to the project's
primary directory through the control-plane move, clears the worktree
hint, re-selects the session, and tells the user where it went. It runs
from a terminal failure and on activation of any session whose directory
is neither a project root nor a managed chat directory; available,
unknown, and failed probes leave everything untouched.
Closes#3338.
* fix(scripts): make oc-dev load again after the changelog cleanup
The changelog cleanup referenced fs.existsSync in a module that imports
existsSync by name and never binds fs, so every oc-dev invocation failed
with "fs is not defined" before reaching its action.
* fix(sessions): probe directory availability on disk, not through OpenCode path resolution
OpenCode's /path never checks that a directory exists: it echoes the
requested path and resolves its project through Git discovery that
swallows errors, so a deleted worktree came back as a valid location and
every missing-directory fallback (draft recovery, archived restore,
session relocation) stayed inert on a real server. The probe now asks
OpenChamber's own /api/fs/list, which stats the path and reports
not-found and not-directory explicitly; anything else stays unknown.
* fix(sidebar): keep a worktree whose directory is gone visible as missing
git keeps a worktree registered after its directory is deleted outside
git and marks it prunable; the list parser ignored that line, so a
deleted worktree looked alive, and nothing in the app asked for a new
listing anyway. The server now reports prunable, the UI keeps such a
worktree in the topology with worktreeStatus missing and a warning icon
on its sidebar group, and relocating a session out of a confirmed-
missing directory raises an in-app topology signal the sidebar
rediscovers on. Dropping the worktree instead would hide every session
that lived there, and a hidden session can never be opened or relocated.
No idle polling is added.
* fix(sessions): never relocate a session to the filesystem root
OpenCode files a directory outside any Git repository under its global
project, whose worktree is the filesystem root. A managed chat whose
directory vanished would otherwise be moved to /. The relocation now
refuses a root destination, and the activation probe recognizes chat
directories through the home-based check as well, so it does not depend
on the chats root having been resolved yet.
* test(sessions): mirror the relocation action in the issue-2039 session-actions mock
session-ui-store now imports relocateSessionFromMissingDirectory, and the
mocked module in this test listed every other action but not that one, so
the file failed on import.
The tab list lived only in per-tab sessionStorage, so a new browser
tab, another device, or cleared storage showed an empty terminal
sidebar while PTYs kept running server-side, and orphans leaked until
the idle sweep. Add GET /api/terminal/sessions and adopt unknown
server sessions into the local tab projection (additive only; a failed
listing changes nothing).
The idle sweep also reaped terminals in background tabs because only
the active tab holds a WebSocket attachment. Add POST
/api/terminal/touch and have open clients periodically refresh
activity for every session their tabs reference.
Keep ARGV0/env-u assertions from this branch and the DA startup-reply
expectations from main's terminal PTY-before-viewport fix.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Terminal creation no longer waits for the Ghostty viewport to report its
size: it starts the PTY immediately with a container/font-derived
provisional size (falling back to 80x24), then resizes once the real
viewport dimensions are known, with a dedupe guard while sizing settles.
Starting the shell earlier means it can emit device/theme queries before
a browser terminal is attached to answer them, so the server now answers
primary device attribute queries itself (Fish blocks ~10s on this at
startup) and bun-pty buffers output emitted before a data subscriber
attaches. Also fixes a few WebSocket transport reconnect races surfaced
by session creation now overlapping renderer setup.
bun-pty merges the OS environ into PTY children, so deleting ARGV0 from the
JS env object alone left the AppImage path in the shell. Wrap Linux PTY
spawns with env -u ARGV0, clear native ARGV0 under Bun via libc unsetenv,
and always clear process.env even when no login-shell snapshot exists.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
AppImage exports ARGV0 into the process environment. zsh treats that as
argv[0] for every external command, which broke Python venv detection in
the integrated terminal and managed OpenCode sessions.
Clear ARGV0 in Electron before login-shell probing, refuse to re-apply it
from shell snapshots, and strip it from terminal PTY and managed OpenCode
launch environments.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Replace the legacy terminal flow with a shared authenticated WebSocket
runtime used across web, desktop, relay, and mobile surfaces.
- introduce the v3 terminal protocol with scoped attachments, snapshots,
ordered output, bounded replay history, reconnects, and explicit lifecycle
- harden PTY creation, restart, resize, close, force-kill, idle cleanup,
shell selection, login mode, environment sanitization, and appearance sync
- add runtime-aware terminal APIs with relay authentication and Electron parity
- add a fullscreen mobile terminal workspace with touch scrolling,
long-press selection, safe-area controls, quick keys, and Ctrl/Alt input
- add terminal selection attachments, preview detection, project actions,
shell settings, and localized UI
- harden Ghostty rendering, resize recovery, Unicode handling, block
characters, line height, and stale-row behavior
- remove the obsolete terminal SSE path and update reverse-proxy guidance
- expand terminal runtime, transport, input, selection, and store coverage
- avoid duplicate web builds when preparing mobile assets in root CI builds