ForkTTY 0.2.0-alpha.13
Pre-release
Pre-release
[0.2.0-alpha.13] - 2026-06-16
Security
- Stop hooks now preserve agent permission-mode warnings when the provider has a later session-end cleanup, while providers without session-end hooks still clear the warning on final stop.
- Terminal smooth-scroll handling now rejects non-finite deltas and caps per-event line replay, preventing oversized synthetic scroll events from monopolizing the UI thread while mouse tracking is active.
- Browser automation now injects and evaluates its driver in an isolated WebKit script world, preventing visited pages from detecting or tampering with
window.__forktty. - Chromium cookie import now verifies the version-24+ encrypted host digest before accepting decrypted values, rejecting malformed or cross-host cookie rows.
- Agent resume PTY spawns now resolve bare provider commands using only absolute
PATHentries before applying the recorded session cwd, preventing relative/emptyPATHentries from executing project-local binaries during restore or resume. - Socket hook correlation now rejects
hook_session_idvalues larger than the metadata text limit before caching them, preventing a local client from retaining many near-request-size session IDs in memory. - Socket-triggered notification dispatch and custom notification command reaping now use bounded queues instead of spawning unbounded OS threads per
notification.create, preventing a local client from exhausting threads by flooding notifications.
Added
forktty doctornow accepts--hooks,--socket, and--packagingscopes for running only the relevant local diagnostics.- First launch now shows a one-time welcome dialog: an informed (default-on) telemetry toggle linking to the privacy notice, and a one-click "Set up agent integration" button that runs
hooks setupandmcp setup. The first anonymous ping is deferred until this dialog is dismissed, so the toggle is always seen before any data leaves the machine; the welcome is recorded in$XDG_STATE_HOME/forktty/welcome-seen.jsonand the update check is skipped on that first launch. - The GTK app now sends at most one anonymous daily usage ping when
telemetry.anonymous_ping = true(the default). The payload contains only schema/kind/app/version/date, can be disabled in Settings or config, and crash uploads remain unimplemented. - The GTK app now checks GitHub Releases at most once per day when
updates.auto_check = true, shows update availability in-app, opens the release page for non-AppImage installs, and can self-update writable AppImages after explicit confirmation by downloading the AppImage plusSHA256SUMS, verifying SHA256, and atomically replacing the current file. - Release AppImages can now embed AppImage update information and ship a matching
.zsyncasset whenAPPIMAGE_UPDATE_INFO=1is used during packaging; release CI enables this and includes.zsyncinSHA256SUMS. - Agent HUD rows now include a Forget action with Undo, so stale tracked sessions can be removed from the HUD without closing the terminal or deleting provider data.
- Agent HUD rows now show an accent unread dot when an agent has produced output you have not viewed since last focusing it, and float those rows up within their lifecycle group — so a finished (idle) agent whose result is still unseen stands out instead of sinking to the bottom of the list.
Fixed
- The terminal pane header now keeps the Close Pane button on the far right of split-pane headers instead of clustering it beside the split and new-tab actions.
- Worktree list/status reporting now treats registered worktree paths replaced by files, symlinks, or invalid directories as unknown instead of clean/dirty.
- Command palette and Settings selection states now use neutral row highlights instead of heavy accent rails.
- Settings toggles are now smaller and use a subtler checked state.
- Settings sidebar navigation is now more compact, with item descriptions kept in tooltips and accessibility labels instead of visible subtitles.
- Settings pages now use shorter page and section copy, hiding redundant section descriptions.
- Settings, Agent HUD, and Notifications now expose a maximize/restore titlebar control and enforce minimum window sizes, while Command Palette windows stay fixed-size.
- Settings now labels the privacy and reset page as Privacy instead of Advanced.
- Agent HUD now relies on its titlebar close button and Esc instead of showing a duplicate Close footer button.
- Agent HUD now opens shorter when sessions are present, keeps its empty state unclipped, uses flatter rows, and gives row actions clearer visual hierarchy.
- Worktree merge selected by worktree name now merges that worktree's branch even when an unrelated local branch shares the worktree's derived name (e.g. worktree
feat-afor branchfeat/aalongside a separatefeat-abranch), matching the worktree the cleanliness check already validates instead of silently merging the wrong branch. - Config loading now normalizes a
notification_commandthat tokenizes to zero words (for example an inline shell comment like"# disabled") to an empty command instead of rejecting it and quarantining the entire config, so a benign command value no longer resets every other setting to defaults. - Nested worktree creation now appends
.worktrees/to.git/info/excludeeven when the existing exclude file contains non-UTF-8 bytes, instead of failing before creating the worktree. - Agent session-end hooks now mark the persisted agent binding as ended when clearing its live status, so agents whose providers emit a session-end event no longer remain shown as running in the Agent HUD.
- Chromium bookmark import, browser bookmark loading, and browser profile metadata loading now reject or skip non-regular files before reading, preventing local FIFO/device paths from blocking the import or profile workflows.
- Socket metadata calls now reject stale explicit
surface_idvalues even whenworkspace_idis valid, oversized request lines return the documentedpayload_too_largecode, and invalid parameter errors use the documentedinvalid_paramcode. - Config recovery now quarantines config paths that resolve to FIFOs without blocking application startup.
- Worktree create now propagates branch lookup errors other than
NotFoundinstead of treating every libgit2 failure as a missing branch. - Creating or attaching a worktree whose registration survived an external deletion of its working directory now prunes the stale registration and recreates the worktree in place, instead of failing with an unresolved-path error;
createadopts the existing branch in this case and never deletes a pre-existing branch during cleanup. ProfileStore::savenow creates thebrowser_profilesdirectory with owner-only (0700) permissions instead of inheriting the umask, so profile metadata is not world-readable on multi-user systems when the directory is first created via the socket API.ProfileStore::savenow hardens an existingbrowser_profilesdirectory owned by the current uid/gid by removing group/other permission bits before writing profile metadata.- Browser import spooling now uses anonymous (unlinked) temp files instead of named temp files, so spooled pre-read data is reclaimed automatically if the process is killed mid-import.
- Browser imports now spool pre-read source data to temporary files instead of retaining every selected profile in memory, preventing large all-source imports from exhausting memory while preserving all-or-nothing read validation before writes begin.
- Update checks now honor HTTP-date
Retry-Afterheaders from GitHub rate-limit responses instead of retrying before the requested deadline. - Restarting an agent pane now resumes the agent session (provider resume argv and recorded cwd) instead of relaunching a plain shell, matching the session-restore and worktree spawn paths.
- Session restore now quarantines a session file containing invalid UTF-8 instead of returning an error that crashed startup on every launch.
- The first-run welcome dialog now stays open when persisting a telemetry opt-out fails (e.g. a read-only config directory), so it cannot silently fall back to the default-enabled ping without giving the user a chance to fix permissions or re-enable telemetry.
- Saving browser bookmarks now creates the profile directory with owner-only (
0700) permissions instead of inheriting the umask, matching the history database directory, so bookmark URLs are not exposed when a profile's first write is a bookmark. - Bookmark entries now bound the stored URL and title to the same size caps as history visits, preventing an oversized imported bookmark from growing
bookmarks.jsonuntil it becomes unreadable. - Shell trampoline detection now keeps scanning after shell options that take a value, so
bash -o vi -c ...andbash --rcfile file -c ...notification commands are rejected instead of bypassing the-cguard. - Shell trampoline detection now recognizes PowerShell's command grammar, so
pwsh -Command ...,pwsh -EncodedCommand ..., andpwsh -CommandWithArgs ...notification commands (and their-c/-e/-ec/-cwaaliases) are rejected instead of bypassing the shell-command guard. PtySession::read_untilnow reportsUnexpectedEofwhen a child exits before the requested bytes arrive, instead of returning partial output as success.forktty hooks testnow sanitizes socket error text before rendering human-readable failures, preventing local socket responses from injecting terminal control sequences.- Browser import is now limited to the in-app Settings workflow and is no longer advertised or accepted over the socket/CLI automation boundary, preventing local socket clients from using ForkTTY to read external browser profile data.
- Notification command validation now rejects
rbash -cshell trampolines instead of allowing restricted Bash aliases to bypass the shell-command guard. - Session restore now quarantines FIFO and other non-regular session paths without blocking application startup.
- Browser history now ignores oversized URLs and truncates oversized page titles before writing to SQLite, preventing web-controlled title or URL churn from causing unbounded history database growth.
- Browser imports now report oversized history URLs as skipped writes instead of counting them as imported rows.
- Browser automation CLI fill now supports
--value-file(with-for stdin) so sensitive values do not have to be exposed in process arguments. - Removed the raw
browser.evalsocket/CLI command so same-user socket clients can no longer execute arbitrary JavaScript inside browser panes. - Agent HUD terminal-tail polling now formats only the bounded tail rows instead of dumping the full scrollback each second, preventing noisy agents from freezing the GTK UI while the HUD is open.
- Browser committed-URI synchronization now rejects URLs over the shared 8 KiB browser URL limit while preserving non-hierarchical URLs such as
about:blank. - OpenCode hooks now sanitize and size-bound plugin payloads before spawning the ForkTTY CLI, preventing oversized tool output from blocking or crashing the OpenCode process before the CLI stdin cap applies.
- Antigravity hook setup now hardens
~/.gemini, the config directory, and generated wrapper directory to owner-only permissions before planning or writing executable hook scripts, preventing local users from replacing wrappers through group/world-writable directories. - Browser profile storage directories are now created with private Unix permissions before WebKit persists cookies, local storage, and cache data.
- Terminal scrollback search now caps stored matches and shows a capped count, avoiding unbounded memory/CPU use on repetitive untrusted terminal output.
- MCP
surface_send_textis now annotated as destructive and open-world, reflecting that terminal input can execute shell commands or interact with files and networks. - Browser imports now read all selected source profiles before preparing destinations or writing data, avoiding partial imports when any later source is unreadable.
- Terminal-originated OSC 9/basic OSC 99 notifications are now rate-limited per surface, preventing untrusted terminal output from spamming desktop notifications or repeatedly spawning
notification_command. - Session locking now creates and hardens the state directory and lock file with private permissions, preventing other local users from reading or pre-locking
session.lockto block startup. - Atomic profile metadata saves now preserve an existing
profiles.jsonfile mode on Unix when ownership matches, and drop group/other bits when replacing with a temp inode owned by a different uid or gid. - Browser history databases and SQLite WAL/SHM sidecars are now created with owner-only file permissions inside owner-only profile directories.
- Bookmark files and corrupt-bookmark backups are now saved with owner-only permissions to avoid exposing sensitive URLs to other local users.
- Stale Ghostty event batches from an old pane spawn are now discarded before they can mark a restarted pane not ready, overwrite its terminal status, or emit stale notifications.
- Browser imports now copy temporary SQLite databases and WAL/SHM sidecars into a private
0700directory with newly-created0600files, preventing local temp-file races from exposing browser data. - Browser-feature socket dispatch fuzz tests now isolate
XDG_DATA_HOME, preventing adversarial method sweeps from clearing a developer's real browser history. forktty eventsnow mirrors lag notices to stderr regardless of the JSON object key order used by the socket server.- Browser import planning now lowercases Unicode titlecase profile names before matching and de-duplicating destinations, preventing duplicate-looking profile creation.
- Command palette shortcut searches such as
ctrl shift cnow match the intended shortcut instead of treating the key token as a fuzzy match against modifier text on earlier commands. - Ctrl+keypad Home/End/PageUp/PageDown are now reserved for tab-navigation accelerators like their non-keypad equivalents instead of being consumed by terminal input handling.
- Retrying Worktree Create for an already-linked branch no longer deletes that existing worktree and branch if terminal spawning fails.
- Sidebar visibility persistence now rebases onto the latest config under a process-wide update lock, avoiding stale background saves that could overwrite newer settings-dialog changes.
- Malformed browser bookmark files are now moved aside after backup so repeated opens cannot create unbounded backup copies.
forktty doctoronce again exits 2 whenever the diagnostics report contains warnings, preserving the documented health-check behavior even without--strict.- Ctrl+click terminal links now open only
http://andhttps://targets, blocking terminal-controlledfile://and custom URI handlers. - AppImage self-updates now create downloaded replacement files with owner-only permissions before checksum verification, closing a local same-group temp-file tampering window under permissive umasks.
- Notification commands using SSH/mosh options that contain
-care no longer rejected as shell trampolines, and a ForkTTY binary built withoutgtk-ghosttynow exits with failure when asked to launch the GTK app. - Worktree and workspace rollback paths now close spawned replacement terminals instead of only forgetting bookkeeping entries, preventing untracked terminal processes after cleanup failures.
- OSC 8 hyperlink lookup now caps URI buffers at 8 KiB and fails closed for larger terminal-provided targets, avoiding attacker-controlled memory growth when resolving links.
- Panic logs are now created in a private state directory with owner-only file permissions, and older permissive logs are rotated before new panic entries are written.
- Terminal text snapshot truncation now treats a zero-byte internal limit as an empty, truncated result instead of disabling truncation.
- Terminal spawning now preserves non-UTF-8 working-directory bytes on Unix instead of converting the cwd through lossy UTF-8.
- Large PTY writes now keep waiting after
poll()reports no writable fd before the per-write deadline, instead of treating the poll timeout as readiness. - PTY
read_untilnow reportsTimedOutwhen the requested bytes do not arrive before its deadline. - Metadata OSC parsing now aborts an unterminated OSC string on a bare
ESC, so OSC 9 notifications and OSC 99 agent metadata that follow in the same PTY chunk are no longer swallowed. - The Worktree dialog no longer overwrites a typed Create/Attach branch name when the asynchronous existing-worktree list finishes loading.
- Agent HUD Resume buttons are re-enabled after a failed resume attempt instead of staying disabled until the HUD is reopened.
- Releasing a terminal text selection after wheel-scrolling mid-drag now preserves the scroll-compensated selection endpoint unless the pointer actually moved.
- Bad config/session quarantine paths are now reserved atomically before rename, avoiding races between simultaneous ForkTTY instances.
- Update checks now strip only one leading
vfrom GitHub release tags, so malformed tags likevv1.2.3are ignored instead of parsed as1.2.3. - Worktree and branch names now reject leading dashes and control characters before reaching git APIs.
- OSC 8 hyperlink lookup now retries with a large enough buffer for long multibyte UTF-8 URIs.
- The MCP stdio server now returns a JSON-RPC parse error and continues after an invalid UTF-8 line instead of ending the session.
- Custom terminal theme colors are now re-applied when an OSC color reset (
OSC 104/110/111) follows an aborted OSC sequence in the same output chunk; previously the reset was swallowed as payload of the aborted sequence and the pane kept the wrong colors. - The MCP stdio server now reads incoming messages through a bounded buffer, so an oversized message is rejected at the 1 MiB limit without first allocating the entire message in memory.
- Clicking an unfocused terminal pane now focuses it and lets the same click start a text selection (or reach the application), instead of swallowing the first click so the drag was lost and had to be repeated.
- Scrolling the wheel or touchpad while dragging a selection now keeps the drag anchored to the same text, like drag-autoscroll already did, instead of silently dropping the in-progress selection; a finished selection is still cleared when the viewport scrolls.
- A terminal color reset (
OSC 110/111/104) immediately followed by an explicit color set in the same output chunk now keeps the application's color, instead of clobbering it with the re-seeded theme color. - Hook/MCP socket requests no longer reject a parameter sent as an explicit JSON
null(e.g.hook_session_id: null) with a type error;nullis now treated as absent, matching the numeric parameter handling. - A completed worktree merge whose post-commit cleanup fails is now reported as success instead of failure, avoiding a retry that would create a duplicate merge commit.
- An agent hook event now still runs its later cleanup actions (clearing a stale status or permission marker) when an earlier action fails transiently, instead of stopping at the first error.
- The
appearance.terminal_renderervalidation error message now listsvte, which is an accepted value. - Closing a terminal pane no longer risks freezing the UI: the dropped PTY session now reaps its killed child on a background thread instead of blocking the GTK main thread in
waitpid, which a child stuck in uninterruptible sleep (D state on a dead NFS/FUSE mount) could otherwise wedge forever. - The PTY read loop now retries a read interrupted by a signal (
EINTR) instead of surfacing it as a spurious error on every pump tick. - Large PTY writes now honor their overall deadline even when repeatedly interrupted by signals, instead of being able to retry indefinitely under a pathological signal rate.
- Socket
surface.read_text/surface.capture_tailno longer block a tokio worker thread while waiting for the GTK main loop: the wait is offloaded viablock_in_place, so many concurrent read requests (as agent hooks issue) can no longer starve the socket server and stall every other request. - Removing a worktree now deletes its working-tree directory before deregistering it from git, so a failed directory removal leaves a recoverable (git-pruneable) registration instead of stranding the directory permanently with no way for git to find it.
- A failed fast-forward merge rollback now logs the underlying ref-reset/HEAD-restore errors instead of silently discarding them, making a wedged repository diagnosable.
- Re-running
worktree.createfor a branch that already has a ForkTTY-supported linked worktree now reopens that worktree instead of failing on the already-created branch, recovering the crash window between Git worktree registration and ForkTTY session persistence. - Concurrent nested worktree creation now serializes updates to
.git/info/exclude, keeping the.worktrees/entry idempotent. - Closing a non-last tab now keeps the model locked through backend close and model removal, so concurrent UI/socket closes cannot observe a half-closed surface.
- Terminal copy, mouse selection, and Select All now omit invisible terminal cells, so escape-hidden text cannot be copied to the clipboard.
The AppImage is the primary download for this alpha; the .deb remains available for Debian/Ubuntu users. Supported distros: see docs/QA.md.
Artifacts (AppImage, .deb, SHA256SUMS) are attached by CI after publish; verify with sha256sum -c SHA256SUMS.