Skip to content

End and reap a local shell when its tab closes, and drop the unused os and process plugins - #154

Open
Sadykhzadeh wants to merge 3 commits into
mainfrom
chore/drop-unused-plugins-and-reap-local-shells
Open

Sadykhzadeh wants to merge 3 commits into
mainfrom
chore/drop-unused-plugins-and-reap-local-shells

Conversation

@Sadykhzadeh

@Sadykhzadeh Sadykhzadeh commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Closes #153.

What this changes

Local shells are ended and reaped

LocalTerminal::child is now a LocalShell, a small wrapper around the portable-pty child that does the following on drop:

  • Ends the shell. It sends portable-pty's kill(): SIGHUP on Unix, which is what closing a terminal window does, and TerminateProcess on Windows.
  • Collects the exit status off the caller's thread. wait() runs on its own short-lived thread, so the async caller is never blocked, even by a shell that ignores SIGHUP.
  • Leaves alone a shell that has already been collected. That is a shell whose exit try_wait has already reported, as reap_exited does. portable-pty signals the raw pid without checking, and once the exit is collected that pid may belong to an unrelated process.

Jobs the user detached from the shell (nohup, disown) are not touched, the same as in any terminal emulator.

The unit tests that called child.kill() by hand after every remove no longer need to. A new Linux test spawns /bin/sh, drops the terminal, and waits for /proc/<pid> to disappear. That only happens once the process has been reaped, so a zombie would fail it.

Unused plugins removed

tauri-plugin-os and tauri-plugin-process are no longer depended on or registered. os:default is no longer granted. This overlaps with #123 on the capability line, and whichever merges second resolves it trivially.

Test plan

  • cargo test -p clavyn-desktop --locked (Linux, local): 108 + 2 passed, including dropping_a_terminal_ends_and_reaps_its_shell
  • cargo update -w --offline: lockfile drops the two plugins and their exclusive deps
  • node scripts/verify/check-comments.mjs: ok
  • CI (macOS and Windows builds; on Windows the drop path goes through TerminateProcess)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…s and process plugins

Removing a local terminal dropped its portable-pty child without waiting
on it, so every closed tab left a zombie for as long as the app ran, and
a shell that ignored the PTY hangup kept running. LocalShell now sends
the hangup itself and reaps the shell on a thread of its own. A shell
whose exit try_wait already collected is never signalled, because its
pid may belong to another process by then.

tauri-plugin-os and tauri-plugin-process were registered and os:default
was granted, but nothing in the frontend imports either plugin, and
restarts go through AppHandle::request_restart. They only added code and
let the page read OS details it has no use for.
@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

TREX TREX could not finish testing

  • Plan what to test
  • Set up the environment and test the changed flows
  • Post test results

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

End and reap local shells when their tabs close

🐞 Bug fix ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• End local shells when their terminals close and reap them without blocking the caller.
• Avoid signaling shells whose exit status was already collected, preventing signals to reused
 process IDs.
• Remove unused OS and process plugins and the OS permission.
Diagram

graph TD
  Close["Tab closes"] --> Terminal["Terminal dropped"] --> Shell["LocalShell drop"] --> Reaped{"Already reaped?"}
  Reaped -- "No" --> Kill["Signal shell"] --> Wait["Background wait"]
  Reaped -- "Yes" --> Skip["Skip signal"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Shared reaper queue
  • ➕ Could avoid starting a thread for every closed shell.
  • ➖ A shell that ignores the termination signal could block a single worker indefinitely; a worker pool adds lifecycle and shutdown complexity.

Recommendation: Keep cleanup with the child-owning LocalShell and use a thread per uncollected shell. It covers removal and refused-creation paths without duplicating cleanup, while isolating potentially blocking waits. A shared reaper is only worth considering if thread creation becomes a measured concern.

Files changed (6) +77 / -87

Bug fix (2) +76 / -16
commands.rsWrap newly spawned local shells +1/-1

Wrap newly spawned local shells

• Constructs local terminals with LocalShell so closing or refusing a terminal triggers child cleanup.

desktop/src-tauri/src/commands.rs

state.rsOwn shell termination and reaping in LocalShell +75/-15

Own shell termination and reaping in LocalShell

• Adds a child wrapper that tracks exits collected by try_wait, signals only uncollected shells on drop, and waits on a separate thread. Updates terminal ownership tests to rely on automatic cleanup and adds a Linux test that checks the dropped shell disappears from /proc.

desktop/src-tauri/src/state.rs

Other (4) +1 / -71
Cargo.lockPrune unused plugin packages +0/-65

Prune unused plugin packages

• Removes both Tauri plugin packages and dependencies exclusive to them from the lockfile.

Cargo.lock

Cargo.tomlRemove OS and process plugin dependencies +0/-2

Remove OS and process plugin dependencies

• Drops the unused tauri-plugin-os and tauri-plugin-process dependencies from the desktop crate.

desktop/src-tauri/Cargo.toml

default.jsonRevoke unused OS capability +1/-2

Revoke unused OS capability

• Removes os:default from the main window's permissions.

desktop/src-tauri/capabilities/default.json

main.rsStop registering unused plugins +0/-2

Stop registering unused plugins

• Removes OS and process plugin initialization from the Tauri application builder.

desktop/src-tauri/src/main.rs

@qodo-code-review

qodo-code-review Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Code Review by Qodo


Comment thread desktop/src-tauri/src/state.rs Outdated
Comment thread desktop/src-tauri/src/state.rs Outdated
Comment thread desktop/src-tauri/src/state.rs Outdated
portable-pty's kill() sends SIGHUP, polls for up to 200 ms and then
kills outright, and the terminal is dropped under the local terminal
map's lock. Closing a tab whose shell took its time therefore held up
every other local terminal operation, and cut short whatever the shell
does on hangup.

The whole shutdown now runs on the reaper thread: the shell gets two
seconds to exit after the PTY hangs up, and only then is it signalled.
If no thread can be started, the shell is ended inline rather than left
as a zombie.
Comment thread desktop/src-tauri/src/state.rs
The PTY hangup only reaches the shell once every copy of the master is
closed, and the output reader keeps one open while it is blocked reading.
A shell busy with a foreground command therefore heard nothing until the
grace period ran out and portable-pty's kill took over. It now gets
SIGHUP as soon as the tab closes, as with a terminal window, and the
grace period starts from there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Closed local terminals leave zombie shells, and two Tauri plugins are registered for nothing

1 participant