Skip to content

feat(terminal): close thread terminals when a thread settles - #4684

Closed
SunkenInTime wants to merge 1 commit into
pingdotgg:mainfrom
SunkenInTime:t3code/close-servers-on-thread-settle
Closed

SunkenInTime wants to merge 1 commit into
pingdotgg:mainfrom
SunkenInTime:t3code/close-servers-on-thread-settle

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Settling a thread means the work is done, but any dev server it started keeps holding its port and CPU. This closes a settled thread's terminals, behind a new server setting closeTerminalsOnThreadSettle, defaulting to off.

Why two mechanisms

Only one of the two settle paths is an event, which shapes the whole design:

  • Explicit ✓ Settle dispatches thread.settle, so ws.ts closes terminals right after the command is accepted. A settle the decider rejects (active session, pending approval, queued turn start) fails before that point, so blocked work never loses its terminals.

  • Auto-settle (inactivity window, merged/closed PR) emits nothing. effectiveSettled is a clock-derived predicate, and its inputs — sidebarAutoSettleAfterDays (a per-client setting) and change-request state (never persisted server-side; it lives only in VcsStatusBroadcaster's per-cwd memory while a client holds a subscription) — are both invisible to the server. The client is therefore the only possible observer of that transition.

So the client reports it via a new terminal.close-settled RPC. The report is advisory, never authoritative: the server re-derives canSettle against the thread shell and ignores any report for a thread still holding live work. It also skips archived threads and threads explicitly pinned active. Idempotent, so several clients reporting the same thread is harmless.

Shared predicate instead of a fourth copy

The settle rules were already duplicated across the server projector, the SQL projection pipeline, and the client reducer. Rather than hand-write a fourth, canSettle and hasQueuedTurnStart move to @t3tools/shared — the one package both sides already import. client-runtime re-exports them, so no import site changes. effectiveSettled itself stays client-side, since it genuinely depends on data the server lacks.

Behavior notes

  • Closes terminal sessions, matching what archive already does. No deleteHistory, so scrollback survives and the terminal can be reopened.
  • Unsettling never restarts anything.
  • Snooze is deliberately excluded — it means "not now", not "done", and a snoozed thread stays active in the data model.
  • Version skew handled by a new settledTerminalCleanup capability flag, so new clients against old servers send nothing rather than erroring.

Testing

  • 3 new server tests: fires on a report, ignores a report for a thread with a running session, ignores when the setting is off. Plus 2 for the explicit path and 2 contract tests.
  • Typecheck clean across all packages; apps/web 1595 passed; @t3tools/contracts 203 passed; lint/fmt clean.
  • Pre-existing failures on this machine (systemd/POSIX-permission/fd tests on Windows) were confirmed unrelated by re-running them on a clean tree.

Not done

  • Mobile does not report auto-settles. Its threadListV2 truncates the settled list to settledLimit and returns only items + hiddenSettledCount, so reporting off it would silently cover just the rendered rows. Correct wiring needs a return-shape change plus test updates. The server already accepts reports from any client.
  • Not yet exercised end-to-end — the decision logic is unit-tested, but no real dev server has been killed by a real settle yet.

🤖 Generated with Claude Code

Note

Close thread terminals when a thread settles

  • Adds a closeTerminalsOnThreadSettle server setting (default false) that triggers terminal closure when a thread settles, configurable via a new toggle in the General settings panel.
  • On explicit settle via thread.settle dispatch, the server closes all thread terminals if the setting is enabled.
  • Adds a terminal.close-settled RPC and a corresponding client-side effect in SidebarV2.tsx that reports auto-settled threads to the server; the server validates settle eligibility using shared canSettle guards before closing.
  • Extracts canSettle, hasQueuedTurnStart, and QUEUED_TURN_START_GRACE_MS into a new shared module at packages/shared/src/threadSettled.ts for consistent evaluation on both client and server.
  • Behavioral Change: terminals that were previously left open after thread settlement will now be closed automatically when the setting is enabled.

Macroscope summarized 9795ea3.

Settling a thread means the work is done, but any dev server it started
keeps holding its port and CPU. This closes a settled thread's terminals,
behind a server setting that defaults to off.

Both settle paths are covered, and they need different mechanisms because
only one of them is an event:

- Explicit settle dispatches thread.settle, so the ws handler closes
  terminals right after the command is accepted. A settle the decider
  rejects (active session, pending approval, queued turn start) fails
  before that point, so blocked work never loses its terminals.

- Auto-settle (inactivity window, merged/closed PR) emits nothing at all:
  effectiveSettled is a clock-derived predicate, and its inputs are a
  per-client setting and change-request state the server never persists.
  The client is therefore the only observer of that transition, so it
  reports it via terminal.close-settled. The report is advisory: the
  server re-derives canSettle against the thread shell and ignores any
  report for a thread still holding live work.

canSettle and hasQueuedTurnStart move to @t3tools/shared so the server
applies the same rules as the client instead of growing another copy of
the settle predicates; client-runtime re-exports them, so import sites
are unchanged.

Terminals close without deleteHistory, so scrollback survives and the
terminal can be reopened. Unsettling never restarts anything.

Snooze is deliberately excluded — it means "not now", not "done".

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e1679699-52db-4169-ace0-1faa7033a6ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 27, 2026
@t3dotgg

t3dotgg commented Aug 28, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

Settling a thread is reversible and does not mean its terminal process has no useful state. Automatically closing terminals can kill shells, servers, or unsaved interactive work, so the cleanup benefit does not justify the data-loss risk.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants