Repository navigation
Conversation
Add apps/desktop/term (hack-desktop-term): an app-owned PTY whose child leads its own session (setsid + controlling tty) and whose Drop ends the whole session (process group and job groups, SIGHUP then SIGKILL), a read-only byte stream (file or process group), and, behind the `ghostty` feature, libghostty-vt 1.0.1 emulation with a GPUI-drawn cell renderer, scrollback cap, follow, search, selection, copy, paste and theme following. Includes the term_spike example and DECISION.md (research, comparison, build/CI plan, uses, security rules, licences). The feature is off by default so desktop CI, which has no Zig 0.17, stays green; DECISION.md lists the workflow change needed to turn it on. Refs HACK-2252 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@codex review |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09b514f9d7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
Review fixes for the terminal surface crate (HACK-2252). - Hold the session leader unreaped until the last signal is sent, so its pid, group id and session id cannot be reused. Observe exit with waitid(WNOWAIT). Signal members outside the leader's group through a pidfd after re-verifying their session (Linux), or after an immediate re-check (macOS, residual race documented). - Move PTY writes to a bounded, cancellable writer thread with non-blocking writes; input that does not fit is refused whole and shown as a notice. Run quit teardown on the background executor. - Replace unbounded channels with queues bounded in pending bytes, with backpressure and cancellation. - Decide the end of a PTY by end of output, not child exit, with a bounded wait when a descendant holds the terminal. - Keep termination and reaping when a worker or reaper thread cannot be started; thread spawning is injectable for tests. - Run stream processes in their own session with the same teardown. - Make the process tests hermetic (emptied environment, fixed PATH, temporary HOME) and signal only recorded pids. - Add term_bench, a rough throughput check. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…the build - Add term/scripts/provision-ghostty.sh: fetch the pinned Zig 0.17.1 and libghostty-vt 1.0.1 archives into a cache directory, verify each against a committed sha256, and print the environment to export. - Point the dependency's download base at an unfetchable URL in the desktop workspace's cargo config, and add a build guard to the crate, so the ghostty feature fails instead of downloading when the inputs are not provisioned. - Add a Linux CI job that provisions, then builds, lints and tests the ghostty feature inside a network namespace with no usable interface. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
libghostty applies its byte limit and line limit together and its 10 MB default byte limit kept only about 5,500 lines on a 200-column grid. Set an explicit 256 MiB byte ceiling so the line bound is the one that applies, and cover it with a test. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…s proven Update DECISION.md for the review fixes: the offline provisioning flow and CI job, session ownership and bounded I/O, the hermetic tests, first throughput numbers, a section on why hosting Ghostty's renderer was not chosen, what the chosen option still has to prove, and an optional spike that would test the alternative. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82578360a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…sed on signals Review round on the terminal surface (HACK-2252), and the owner's panel requirements. Review fixes: - Signals fail closed. A signal goes only through a handle whose identity cannot be recycled: killpg on the anchored leader group everywhere, a pidfd for other session members on Linux. Where there is no such handle (macOS, Linux before 5.3) members outside the leader group are never signalled by number; they are found by a session scan and returned in Teardown::survivors. No kill(pid) after a check remains, test cleanup included. The process table is injectable so tests can replace a pid's identity between verification and signalling. - Any input or notice change notifies the view, so a rejected paste to a quiet child draws its notice in the next frame. - Quit teardown is registered and awaited for process-backed streams as well as PTY sessions. Panel requirements: - launch: session kinds (logs, container shell, host shell, host shell with env) with labels and badges, and the command each runs. The app sets only TERM, COLORTERM and TERM_PROGRAM and passes validated names; the CLI injects env values inside the PTY. A container shell tries bash, then sh when bash cannot start. - ghostty_config: read font, colours, palette, cursor style and padding from the user's Ghostty config (includes and themes followed), falling back to app tokens. Only those keys are parsed and nothing is logged. - panel and actions: a tabbed panel with toggle, new tab, close tab, next and previous tab, maximize and find. No keys are bound here. - term_spike shows all four tab kinds in one panel and renders each. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ed teardown Record how each tab kind is started and why the CLI stays the only thing that touches env values, why this is preferred to attaching to tmux, three numbered CLI requests, tab identity and badges, the Ghostty config rules, the keyboard actions, what the new renders and tests show, and the macOS limit that fail-closed signalling leaves (reported as survivors). Replace the two spike renders with one per tab kind. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…ctions Review round on the terminal surface (HACK-2252). - Test helpers never signal a pid by number. The "no trace" assertion only reports; a test that may leave a process running holds a pidfd handle for that session. - The Ghostty config reader is bounded end to end. A file is identified by its canonical path before it is opened and is never read twice. It is opened once, without blocking and only if the handle is a regular file, and read through a 1 MiB limit on that handle. One load reads at most 4 MiB and 64 files across config files, includes and the theme. - An empty `palette =` resets the palette, the theme's entries included. - The view binds no keys. Copy, paste, find, find navigation, scrolling and follow are actions with public methods; the view keeps only key forwarding to the PTY and text entry for its find field. The example binds the keys from the app's design, per platform. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…onfig reads Document every action with the bindings from the app's design, the key contexts for binding predicates, the reader's limits and the palette reset, and the tests added for them. Re-render the panel screenshots with the example's new key hints. Refs HACK-2252 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Ready for lead: 410b059 What it is: a new
What the crate does:
The Cleanup when a tab or the app closes: fail-closed.
Ran (Linux, worker and reviewer):
Hosted CI:
Review: one persistent Codex reviewer over 4 rounds, the same reviewer as #861.
CLI requests: numbered in the PR body.
Not verified:
Defaulted decisions:
|
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
|
Codex couldn't complete this request. Try again later. |
…rminal itself On macOS the prompt's powerline separators and Nerd Font symbols drew as missing-glyph boxes when the terminal font had no glyph for them. Box drawing, block elements and the powerline separators are now drawn as cell-exact geometry on whole device pixels, so neighbouring cells join without seams. Other prompt symbols come from Symbols Nerd Font Mono 3.5.1: the provisioning script fetches and checksums it, the build embeds it, and the view fills its outlines as vector paths. Outlines are read with ttf-parser because GPUI's Linux text system refuses a font with no glyph for "m", and a system font may define the same private-use code points as something else. A pixel test renders a row of these code points offscreen and fails on a blank or missing-glyph cell, on an icon that did not come from the bundled font, and on geometry that does not fit its cell. The term CI job installs a software Vulkan driver and requires the renderer. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…new renders Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
Symbols from the bundled font were sized by the em and the ascender, but some glyphs have ink outside those metrics (U+F0DC and U+F0DE rise above the ascender), so they could paint into the row above. Diagonal box drawing strokes ran past the cell's corners. A symbol is now placed by its ink box: the scale is reduced until the ink fits its room and the glyph is moved by as little as brings it inside. Symbols and the geometry glyphs are also clipped to their room. A double-width symbol such as U+26A1 now draws in the two cells the emulator gave it; it was fitted to one because its spacer cell did not count as blank. TermFont gains `symbols` (BundledSymbols::WhenMissing by default, Always to draw every bundled symbol from the bundled font), which the double-width render test needs on machines whose fonts have U+26A1. The render tests now check every pixel around each glyph's room and the placement of each symbol's ink, and a unit test places every glyph of the font in eight room sizes. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…er the Unlicense Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5d5868aa5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
With find open, new output that added matches updated the highlights
but not the count beside the find field: the view copied the search's
state before the search had run on the new output. If the source then
went quiet, the stale count ("no matches" next to highlighted matches,
for one) stayed on screen.
The view now runs the search in render, before reading the state the
overlay shows, so the count and the frame's highlights come from the
same pass. If a resize during layout changes the count, another frame
is requested.
Rendered reports the find state and the number of highlights a frame
drew, for the regression test: find open, output adds matches, the
source goes quiet, and the count shown equals the highlights with no
further frame.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
…er frame With find open every render ran the libghostty search twice, once for the count and once for the frame's highlights, and each run checks every match it holds. With 390,000 matches in a 10,000-line scrollback an unchanged redraw took about 20 ms in a dev build. The emulator now counts the changes a search depends on (output, a resize, a viewport move, the needle, the selected match) and runs the search at most once per change. The count and the frame's highlights come from that one pass, the frame carries the state it was built with, and redrawing an unchanged terminal runs nothing: 1.8 ms in the same case. Highlights for rows below the viewport, which libghostty lists when they share a page with it, are no longer put in the frame. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e518f0bf4c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let Some(shutdown) = view.update(cx, |v, _| v.take_shutdown()) { | ||
| self.closing.push(shutdown.terminate_in_background()); | ||
| cx.spawn(async move |this, cx| { |
There was a problem hiding this comment.
Include pending tab teardowns in application shutdown
If a tab containing a process that ignores SIGHUP/SIGTERM is closed and the application quits immediately afterward, that process can survive the application. terminate_in_background waits the 300 ms TERMINATE_GRACE before escalating, but the detached polling task here is not awaited during quit. The view's on_app_quit handler cannot help because take_shutdown has already removed its backend, and application exit can terminate the reaper before SIGKILL is sent. Retain shutdown ownership for closing tabs and include their pending teardowns in the awaited quit cleanup, with escalation that fits the quit deadline.
Useful? React with 👍 / 👎.
🛡️ Codex Security Review · Automatically triggeredSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
Summary
Unit B of the desktop app (HACK-2252, child of HACK-2214): the terminal surface, now a per-project bottom panel with tabs (owner decision, Nov 15 2026). Linear status is reported here because Linear may be unreachable.
Crate
apps/desktop/term(hack-desktop-term):pty,session,stream: an app-owned PTY whose child leads its own session; closing it ends the session and never blocks the caller. Read-only byte sources for logs.launch: what a tab is (logs, container shell, host shell, host shell with env), its label and badge, and the command each kind runs.ghostty_config: the user's Ghostty font, colours, cursor and padding, read from their config.emulator,terminal,view,panel,actions(featureghostty): libghostty-vt 1.0.1 state painted by a GPUI cell renderer, in a tabbed panel with keyboard actions.sprite,glyphs: box drawing, blocks and powerline separators drawn as cell-exact geometry; other prompt symbols filled from a bundled symbols font, so they do not depend on the user's font or the platform's font fallback.term_spike(all four tab kinds in one panel) andterm_bench.term/DECISION.md: research, comparison, why not option (a), build and CI, sessions, CLI requests, ownership, tests, performance, uses, security rules, licences.CLI requests
Three small CLI changes would close gaps the app must not work around:
hack env list --json: addoverlays, the names of the overlays that exist. Today the payload has the selection (env_selection) and thescopes, but only the selected overlay's files, so the app cannot offer a list of overlays to open a shell with. Names only.hack host shell: whenSHELLis unset or empty, use the user's passwd shell before falling back to/bin/sh. Run for real withSHELLunset, it started/bin/sh.hack exec: document and test that a command that cannot be started in the container exits 127 (or 126) on both backends. The bash-to-sh fallback depends on it, and today it is the engine's behaviour, not the CLI's contract.Reviewer round at
330f84a8: one search pass per changeWith find open, every render ran the libghostty search twice (once for the count, once inside
frame()), and each run checks every match it holds. Fixed ine518f0bf.One pass per frame.
frame()andsearch_refresh()share one pass, andFramenow carries theSearchStateits highlights were built with.No pass when nothing changed. The emulator keeps a revision counter, bumped by output, a resize, a viewport move, a new needle and a new selected match. The search runs at most once per revision and the state is reused until the next one.
Regression tests.
Emulator::search_runs()counts runs.the_search_runs_once_per_change_and_not_at_all_for_an_unchanged_redraw(emulator) covers unchanged redraws, no-op calls, and exactly one pass after output, resize, scroll, follow and a step.redrawing_an_unchanged_view_with_find_open_does_not_run_the_search(view) draws three more frames and requires zero runs. Tried against three faults (cache never used; output not counted; scroll not counted): each fails a test.Timing, same dense case (dev build, 10,000-line scrollback, 390,000 matches, unchanged terminal, medians of 30):
330f84a8)e518f0bf)A frame after one new line of output takes 12.4 ms: one run, where it was two. That run is libghostty's own cost for this many matches and is not made incremental here.
The quiet-source count regression still passes, unchanged.
One thing fixed on the way, flagged: libghostty lists matches that share a page with the viewport but lie below it, and those were put in the frame as highlights on rows that are not on screen. They are now left out, so
Rendered::highlightsis the number on screen as documented.Bot thread at
a5d5868a: the find count lagged behind the highlightsFixed in
330f84a8, replied on the thread and resolved it.render, before reading the state the overlay shows (Emulator::search_refresh), so the count and the frame's highlights come from the same pass. If a resize during layout changes the count, the view requests another frame.the_find_count_matches_the_highlights_when_output_arrives_and_then_stops(tests/view.rs). With the old copy put back it fails with a count of 1 beside 3 highlights.Renderednow reports the find state and the number of highlights a frame drew, which the test reads.Reviewer round at
610cb216(three items)933705dd).SymbolOutline::place). U+F0DC and U+F0DE, whose ink rises above the ascender, now sit wholly inside their room.symbol_cells: the emulator's two cells first, then the blank-cell rule for a narrow symbol. A double-width symbol is not widened into a third cell (flagged: it already has the two cells that rule exists to give). New render testa_double_width_symbol_is_drawn_in_both_of_its_cellsfor U+26A1, plus a unit test of the rule.One addition, flagged:
TermFontgainssymbols: BundledSymbols(WhenMissingby default,Alwaysto draw every bundled symbol from the bundled font). The double-width test needs it, because many fonts have U+26A1 and the default rule would then use the terminal's font. Default behaviour is unchanged.Each new check was tried against the fault it is for: without the sprite clip the diagonal paints 4 pixels outside its cell; with the old placement U+F0DC is cut off at the top of its room and the unit test fails; with a one-cell room the double-width symbol is half the size. Not separately detectable: removing only the symbol clip, because the placement already keeps the ink inside.
Three renders changed slightly (host dark, host light, host env): the folder icon is fitted by its ink. The other three are byte-identical.
First Mac result at
410b0590: prompt symbols drew as boxed question marksOn Apple silicon (macOS 26.5.1) provisioning, the build, the window and
--screenshotsworked, but powerline and Nerd Font symbols in the prompt drew as missing-glyph boxes. Fixed inb33c5421; the fix has not been run on a Mac yet.sprite: U+2500 to U+259F (box drawing, block elements) and U+E0B0 to U+E0BF (powerline separators) are drawn as rectangles and paths on whole device pixels. Neighbouring cells join with no seam at any font size. Cell backgrounds, selection and the cursor now use the same pixel edges.glyphs: every other symbol comes from Symbols Nerd Font Mono 3.5.1 when the terminal's font lacks it. A private-use code point always comes from the bundled font unless the terminal font is itself a Nerd Font and has the glyph.tests/render.rs(extended in the round above): draws the two solid arrows, branch U+E0A0, U+F418, U+E718, U+F07B, four box-drawing characters, two blocks and a known-missing code point offscreen. It fails if a cell is blank, matches the missing-glyph render, equals another icon, is a speck, did not come from the bundled font, does not have the bundled glyph's proportions, or (for the geometry) does not fit its cell to the pixel. Checked by breaking the code five ways; each break fails the test.Departures from the suggested default, flagged:
add_fonts. GPUI's Linux text system refuses any font with no glyph for "m", so a symbols-only font can never be selected there, and a system font on the development machine defined U+E0A0 as an unrelated glyph, which is what got drawn. Instead the crate reads the outlines withttf-parserand fills them as GPUI vector paths. This is the same code on macOS and Linux and uses no platform font fallback, which is the part that failed on the Mac.provision-ghostty.shfetches the release archive and checks two sha256 sums (archive and font file);build.rschecks the font file again and embeds it. It adds 2.6 MB to a binary built with theghosttyfeature. The lead must runprovision-ghostty.shagain to get it.HACK_TERM_REQUIRE_RENDERER=1, so the pixel test must run there. Without that variable the test reports "NOT RUN" and passes on a machine with no renderer.Licence: the Nerd Fonts archive is MIT. The font combines icon sets under their own licences: Font Awesome and Codicons (CC BY 4.0), Material Design Icons (Apache-2.0), Pomicons and Weather Icons (SIL OFL 1.1), Font Logos (the Unlicense, public domain), Devicons, Octicons, Powerline symbols, Seti-UI and others (MIT). All allow embedding; the app's licence notices must list them for attribution.
Reviewer round at
f6e8aced(four items)killed itassert_no_traceonly reports the pid; it signals nothing. A test that may leave a process running holds a pidfd handle for that session, which cleans up when the test unwinds.Copy,Paste,Find,FindOlder,FindNewer,CloseFindand the scroll and follow actions are actions with public methods. The view keeps only key forwarding to the PTY and text entry for its find field. The example binds the design's keys per platform (table below).Find,CloseFind,PasteandCopyactions work; after the app binds Ctrl-Shift-F toFind, the key opens it.palette =should reset the paletteBindings, in the example and for the app's keymap (from the app's design):
The example also binds keys the design does not list yet, as its own choice (flagged): Enter, Shift-Enter and Escape in the find field; Shift-PageUp and Shift-PageDown for scrollback; PageUp, PageDown, Home, End and
fin a log view. Without those bindings an app has no key for them.Reviewer round at
82578360(three items, accepted)kill(pid)is still a reuse race (macOS, and the Linux fallback without pidfd)killpgon the anchored leader group on every platform, and a pidfd for other session members on Linux. Where there is no such handle (macOS, Linux before 5.3), members outside the leader group are never signalled by number; they are found by a session scan and returned inTeardown::survivors. Nokill(pid)after a check remains anywhere. Test cleanup uses pidfds on Linux and signals nothing on macOS.App::shutdownwith a stream process that ignores SIGTERM and has a descendant that does too. Both are gone and reaped when shutdown returns. Fails without the fix. Same for a PTY session that ignores SIGHUP.The macOS limit this leaves (documented in
session.rsand DECISION.md; now confirmed on hosted macOS, see below): a process in another process group of the session that ignores or never receives SIGHUP can outlive a closed tab, as it would outlive any terminal app. It is reported, not ended. The panel shows how many processes outlived closed tabs.Owner requirements A to E
A. Sessions. The app owns the PTY and runs the CLI in it. It sets only
TERM=xterm-256color,COLORTERM=truecolorandTERM_PROGRAM=hack, and passes only subcommands, validated names and a shell name. It never builds an environment and never sees a value.hack exec <service> -- bash, then-- shif bash cannot start (exit 126 or 127 straight away, before any input)$SHELL, else passwd, else/bin/sh) as a login shell by theargv[0]convention (-zsh), so profile and rc files loadhack host shell --env <overlay> --scope <scope>; the CLI injects the values and starts$SHELL -lChecked against the CLI source on
next, and run for real in a throwaway project with a temporary home:hack host shell --env qa --scope apion a pseudo-terminal: the profile loaded, its prompt showed, the shell was interactive, global and service-scoped values were present,TERM_PROGRAMandCOLORTERMcame through, and the working directory was the project root. So it can start the user's interactive login shell with rc files whenSHELLis set.hack env list --json: returnsenv_selection(requested,default,effective),scopes,selected_scope,files, andvarswith names and metadata and no value field.B. Tab identity and badge.
SessionKindgiveslogs,api · sh,host · zsh, and the badgeenv staging · apiwith a warning tone when the overlay is not the default. Names only.C. Look. The user's Ghostty config is read for font family (with fallbacks), font size, background, foreground, palette, cursor style and padding.
config-fileincludes andtheme(name, absolute path,light:A,dark:B) are followed. Only those keys are parsed, nothing is logged, and anything unset falls back to the app's tokens.D. Verified in the spike. Renders below; each item also has a test (table in DECISION.md).
E. Keyboard actions. Panel:
TogglePanel,NewTab,CloseTab,NextTab,PrevTab,ToggleMaximize. Terminal:Copy,Paste,Find,FindOlder,FindNewer,CloseFind, and scroll and follow actions. All in thehack_termnamespace. The crate binds no keys.Renders (offscreen GPUI capture on Linux)
Host login shell with a themed prompt, truecolour, bold and italic, wide glyphs, prompt symbols from the bundled font, drawn separators and an OSC 8 link:
The same tab with a fixture Ghostty config applied (Nerd Font with real italics, theme colours, bar cursor, padding):
Host shell with env, and its badge (a stand-in script in place of
hack host shell):Container shell after falling back from bash to sh (a stand-in in place of
hack exec), and the read-only log stream with find:Light theme: panel-host-light.png.
Recommendation (unchanged)
libghostty-vt for emulation, cells drawn with GPUI (option b). The comparison table, "Why not (a), even though it matches Ghostty's pixels", what (b) still has to prove, and the optional spike of (a) are in DECISION.md.
flowchart LR subgraph Tabs L[Logs: read-only stream] C[Container shell: hack exec service -- bash or sh] H[Host shell: login shell] E[Host shell with env: hack host shell --env --scope] end L -->|bounded byte queue| EM[Emulator: libghostty-vt 1.0.1] C -->|app-owned PTY| EM H -->|app-owned PTY| EM E -->|app-owned PTY| EM K[Keys and paste] -->|bounded input queue| C K --> H K --> E EM -->|Frame| R[GPUI cells in the tabbed panel] G[User's Ghostty config] -->|font, colours, cursor, padding| RFirst performance numbers (
term_bench, release, one Linux desktop; a sanity check): about 220 MiB/s of emulation, about 10,000 full 200×60 repaints per second, and about 4 ms of CPU per full-screen change (0.9 ms frame build + 3 ms paint). Not GPU time, not key-to-screen latency, not a Mac.Defaults I chose (flagged)
TERMisxterm-256coloreverywhere.xterm-ghosttyis available as an opt-in for host shells and is used only when its terminfo entry is installed. Reason: containers and ssh targets rarely have it.argv[0](-zsh), not-l -i: every common shell honours it, and flags differ between shells.$SHELL, elsesh) until request 2 lands.NewTabis an event, not a default: the app chooses the kind.ghosttyfeature is off by default; inputs are provisioned and checksummed, not vendored;apps/desktop/.cargo/config.tomlmakes the dependency fail instead of download; queues are 1 MiB; SIGKILL grace is 300 ms on close and 100 ms on quit.Disclosure (unchanged): during the first spike, one
pkill -f "^sleep 300$"was run on the development host. No pattern kills are used any more. Tests signal only through pidfds, or a child of the test process that is still unreaped (its pid is anchored).A side effect on the development host during this round: one cargo run with an overridden home made rustup install toolchain 1.98.1 into the default rustup home. Nothing in the repository depends on it.
Licences
This round adds no package to
Cargo.lock:ttf-parser0.25.1 (MIT OR Apache-2.0) andsha20.10.9 (MIT OR Apache-2.0, build script only) were already there through GPUI and are now direct, exactly pinned dependencies of theghosttyfeature. It adds one embedded asset, Symbols Nerd Font Mono 3.5.1 (see the licence note above). The table of every package this branch adds toCargo.lockis in DECISION.md ("Licences of new dependencies"):ghostty-vtandghostty-vt-sys(Apache-2.0, linking libghostty-vt, MIT), their build-script dependencies (MIT, Apache-2.0, CDLA-Permissive-2.0, CC0), and nothing copyleft. Zig 0.17.1 (MIT) is a build tool and is not shipped.How to run
Verified on Linux (x86_64), at
e518f0bf4cf395f912edbd48c5496a22475fcabacargo fmt --all --check;cargo clippy --locked --all-targets -- -D warningswith no features,hack-desktop/screenshots,hack-desktop-term/ghosttyandhack-desktop-term/screenshots;cargo build --locked -p hack-desktop.bash scripts/test.sh: 169/169 default, 201/201 withhack-desktop-term/ghostty, 209/209 withhack-desktop-term/screenshots(both pixel tests required and passing).no-network.sh check-ghostty.shwith no display variables set and the renderer required: build, both clippy runs and 97/97 crate tests, both pixel tests among them, in a namespace with only a down loopback.next081a5263is merged in (cbc9a800); the two Bun contract tests passed with the pinned Bun at610cb216(89 pass). This round changed no TypeScript.cargo clippy --target aarch64-apple-darwin -p hack-desktop-term --all-targets -- -D warningspasses (default features only; theghosttyfeature cannot be cross-built here).a5d5868a; this round changed no painting code.Verified on hosted CI (earlier heads)
All three desktop jobs passed on
f6e8acedand again on410b0590. Neither includes the glyph work. On330f84a8the Linux term job passed: it built offline, ran the software Vulkan driver and the required pixel tests, so the glyph tests and the find-count regression have run on a hosted Linux runner. The macOS job for that head was cancelled by the next push. The macOS job (macos-15) ran 136/136, including the tests whose macOS behaviour had never run before:a_job_in_another_process_group_is_ended_or_reported: on macOS the job is left alone and named insurvivors.the_leader_stays_unreaped_until_signalling_is_doneand the fake-table fail-closed tests.zsh_loads_its_login_and_interactive_files_when_it_is_installed: real zsh as a login shell, profile and rc loaded, alias works.a_host_shell_is_an_interactive_login_shell_that_loads_the_users_profileagainst macOS'ssh.The offline Linux job ran 73/73 inside its network namespace.
Not verified
e518f0bfhad only just started when this was written.ghosttyfeature has no macOS CI job. On a Mac it has been built and run only by the lead, at410b0590, before any of the glyph work. ⌘ bindings were never pressed.hack execin a container: not run (no Docker in this work). The container tab was tested and rendered with a stand-in script.cursor-colorandselection-*from the Ghostty config.macOS commands for the lead
What to capture:
check-ghostty.sh(the emulator, terminal and headless view tests) has never run on a Mac.term_benchlines.vimstill copy rather than reach the program?printenv SOME_KEYshow the overlay value, and is the badge right?sleep 100: the window must stay responsive and show "input not sent".sleep 300 &and note its pid, close the tab, thenps -p PID: it should be gone. Then try(set -m; nohup sleep 301 &): on macOS this one may survive, and the panel should say one process is still running.--screenshotstoo and compare with the PNGs in this PR): are the powerline separators, the branch symbol and the folder and language icons real glyphs now, with no boxed question marks? Do separators meet the coloured segments with no hairline gap? Are icons a sensible size next to the text?🤖 Generated with Claude Code