Skip to content

ForkTTY 0.2.0-alpha.13

Pre-release
Pre-release

Choose a tag to compare

@Lucenx9 Lucenx9 released this 16 Jun 12:59
· 102 commits to main since this 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 PATH entries before applying the recorded session cwd, preventing relative/empty PATH entries from executing project-local binaries during restore or resume.
  • Socket hook correlation now rejects hook_session_id values 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 doctor now accepts --hooks, --socket, and --packaging scopes 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 setup and mcp 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.json and 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 plus SHA256SUMS, verifying SHA256, and atomically replacing the current file.
  • Release AppImages can now embed AppImage update information and ship a matching .zsync asset when APPIMAGE_UPDATE_INFO=1 is used during packaging; release CI enables this and includes .zsync in SHA256SUMS.
  • 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-a for branch feat/a alongside a separate feat-a branch), matching the worktree the cleanliness check already validates instead of silently merging the wrong branch.
  • Config loading now normalizes a notification_command that 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/exclude even 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_id values even when workspace_id is valid, oversized request lines return the documented payload_too_large code, and invalid parameter errors use the documented invalid_param code.
  • Config recovery now quarantines config paths that resolve to FIFOs without blocking application startup.
  • Worktree create now propagates branch lookup errors other than NotFound instead 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; create adopts the existing branch in this case and never deletes a pre-existing branch during cleanup.
  • ProfileStore::save now creates the browser_profiles directory 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::save now hardens an existing browser_profiles directory 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-After headers 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.json until it becomes unreadable.
  • Shell trampoline detection now keeps scanning after shell options that take a value, so bash -o vi -c ... and bash --rcfile file -c ... notification commands are rejected instead of bypassing the -c guard.
  • Shell trampoline detection now recognizes PowerShell's command grammar, so pwsh -Command ..., pwsh -EncodedCommand ..., and pwsh -CommandWithArgs ... notification commands (and their -c/-e/-ec/-cwa aliases) are rejected instead of bypassing the shell-command guard.
  • PtySession::read_until now reports UnexpectedEof when a child exits before the requested bytes arrive, instead of returning partial output as success.
  • forktty hooks test now 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 -c shell 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.eval socket/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_text is 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.lock to block startup.
  • Atomic profile metadata saves now preserve an existing profiles.json file 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 0700 directory with newly-created 0600 files, 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 events now 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 c now 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 doctor once 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:// and https:// targets, blocking terminal-controlled file:// 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 -c are no longer rejected as shell trampolines, and a ForkTTY binary built without gtk-ghostty now 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_until now reports TimedOut when 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 v from GitHub release tags, so malformed tags like vv1.2.3 are ignored instead of parsed as 1.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; null is 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_renderer validation error message now lists vte, 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_tail no longer block a tokio worker thread while waiting for the GTK main loop: the wait is offloaded via block_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.create for 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.