Skip to content

Handle terminal exit writes and auto-close exited terminal drawer - #115

Merged
juliusmarminge merged 2 commits into
mainfrom
codething/0cbde587
Feb 28, 2026
Merged

Handle terminal exit writes and auto-close exited terminal drawer#115
juliusmarminge merged 2 commits into
mainfrom
codething/0cbde587

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Feb 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Ignore terminal write calls when a session has already exited, instead of throwing an error.
  • Add a server test to verify trailing writes after process exit are safely ignored.
  • Update terminal drawer behavior to auto-close an exited terminal session in the UI.
  • Prevent duplicate exit handling in the terminal viewport by guarding and resetting exit state on restart.

Testing

  • Added unit test: apps/server/src/terminal/Layers/Manager.test.ts (ignores trailing writes after terminal exit).
  • Not run: full project lint script.
  • Not run: full test suite.

Note

Medium Risk
Medium risk because it changes terminal I/O error handling and introduces new UI side effects (auto-closing) on exited events; could mask legitimate client/server state bugs or close tabs unexpectedly if exit events are misfired/duplicated.

Overview
Prevents server-side terminal write calls from throwing after a session has already exited by returning early when status === "exited".

Adds a unit test ensuring post-exit writes are ignored, and updates the web ThreadTerminalDrawer/TerminalViewport to auto-close a terminal tab when it receives an exited event, with guards to avoid duplicate exit handling and to reset the exit guard on restart.

Written by Cursor Bugbot for commit e6fd90d. This will update automatically on new commits. Configure here.

Note

Ignore writes after terminal exit in TerminalManagerRuntime.write and auto-close ThreadTerminalDrawer when a session exits

Update server write behavior to no-op on exited sessions and add client-side auto-close on terminal exited events. Add a test covering trailing writes after process exit in Manager.test.ts, adjust TerminalManagerRuntime.write in Manager.ts, and wire onSessionExited to close drawers in ThreadTerminalDrawer.tsx.

📍Where to Start

Start with TerminalManagerRuntime.write in Manager.ts, then review the exit handling flow in ThreadTerminalDrawer.tsx, and validate behavior via the new test in Manager.test.ts.

Macroscope summarized e6fd90d.

Summary by CodeRabbit

  • Bug Fixes

    • Terminal sessions now gracefully handle write attempts after exit, preventing errors and ensuring clean termination.
  • New Features

    • Terminals automatically close when the session exits, improving cleanup and user experience.

- Ignore late write calls after a terminal session has already exited
- Add regression test for trailing writes after exit
- Close terminal drawer session once an exit event is handled
@coderabbitai

coderabbitai Bot commented Feb 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

These changes implement terminal session exit handling across frontend and backend. The backend prevents write errors when a session has exited, a new test validates this behavior, and the frontend threads an exit callback through terminal components to trigger drawer closure.

Changes

Cohort / File(s) Summary
Backend Terminal Manager
apps/server/src/terminal/Layers/Manager.ts, apps/server/src/terminal/Layers/Manager.test.ts
Added early return in Manager.write when session status is "exited" to silently ignore trailing writes post-termination. New test validates that write attempts after terminal exit resolve to undefined without side effects.
Frontend Session Exit Handling
apps/web/src/components/ThreadTerminalDrawer.tsx
Introduced onSessionExited callback prop to TerminalViewport, threaded from ThreadTerminalDrawer. Coordinates single-time exit handling via hasHandledExitRef, resets on terminal restart events, and triggers drawer closure asynchronously when session exits.

Sequence Diagram(s)

sequenceDiagram
    participant Drawer as ThreadTerminalDrawer
    participant Viewport as TerminalViewport
    participant Terminal as Terminal Process
    participant Manager as Manager

    Terminal->>Manager: emit "exited" event
    Manager->>Viewport: terminal "exited" event
    Viewport->>Viewport: check hasHandledExitRef
    Note over Viewport: Set hasHandledExitRef = true
    Viewport->>Viewport: setTimeout(() => onSessionExited(), 0)
    Viewport->>Drawer: onSessionExited callback
    Drawer->>Drawer: onCloseTerminal(terminalId)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: handling terminal exit writes (in Manager.ts) and auto-closing the terminal drawer (in ThreadTerminalDrawer.tsx).
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codething/0cbde587

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/web/src/components/ThreadTerminalDrawer.tsx (1)

347-353: Guard deferred close callback after unmount.

The zero-delay callback can still fire after cleanup. Add a disposed check before invoking onSessionExitedRef.current() to avoid stale close calls.

Suggested patch
         hasHandledExitRef.current = true;
         window.setTimeout(() => {
+          if (disposed) return;
           onSessionExitedRef.current();
         }, 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ThreadTerminalDrawer.tsx` around lines 347 - 353, The
deferred zero-delay callback can run after the component unmounts; update the
timeout callback in ThreadTerminalDrawer so it checks a disposal flag before
calling onSessionExitedRef.current(): add or reuse a disposedRef (e.g., const
disposedRef = useRef(false) and set disposedRef.current = true in the
cleanup/unmount), then change the setTimeout handler to if (!disposedRef.current
&& !hasHandledExitRef.current) { hasHandledExitRef.current = true;
onSessionExitedRef.current(); } (or at minimum check disposedRef.current before
invoking onSessionExitedRef.current()) to avoid stale close calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/components/ThreadTerminalDrawer.tsx`:
- Around line 347-353: The deferred zero-delay callback can run after the
component unmounts; update the timeout callback in ThreadTerminalDrawer so it
checks a disposal flag before calling onSessionExitedRef.current(): add or reuse
a disposedRef (e.g., const disposedRef = useRef(false) and set
disposedRef.current = true in the cleanup/unmount), then change the setTimeout
handler to if (!disposedRef.current && !hasHandledExitRef.current) {
hasHandledExitRef.current = true; onSessionExitedRef.current(); } (or at minimum
check disposedRef.current before invoking onSessionExitedRef.current()) to avoid
stale close calls.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6c5a4a and dd5b985.

📒 Files selected for processing (3)
  • apps/server/src/terminal/Layers/Manager.test.ts
  • apps/server/src/terminal/Layers/Manager.ts
  • apps/web/src/components/ThreadTerminalDrawer.tsx

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
- Skip `onSessionExited` if exit handling was reset before timeout runs
- Prevent stale async exit callbacks from firing in terminal drawer
@juliusmarminge
juliusmarminge merged commit 21c823e into main Feb 28, 2026
4 checks passed
radroid added a commit to radroid/t3code that referenced this pull request Aug 8, 2026
…eep reopened

Fallout from #56, caught by re-checking the alert list after it merged rather than assuming the count only goes down.

Before the sweep the tree had one nanoid@3.3.12, and GHSA pingdotgg#115/pingdotgg#116 against it sat auto-dismissed by GitHub's auto-triage rule — it was scoped as a development dependency. astro 7.2.0 restructured its tree, adding an already-fixed nanoid@3.3.17 alongside the old copy and flipping that copy's scope to runtime, which took it out from under the rule and reopened both alerts.

The sweep did not introduce a vulnerability — 3.3.12 was there before and was always affected — but it turned a suppressed finding into a live one.

One override, "nanoid@3": ^3.3.17, dedupes onto the version already in the tree. Both advisories want <= 3.3.17, so this clears both.

Ledger figures refreshed in the same commit per SEAMS.md's self-reference rule.
github-actions Bot pushed a commit to radroid/t3code that referenced this pull request Aug 10, 2026
…eep reopened

Fallout from #56, caught by re-checking the alert list after it merged rather than assuming the count only goes down.

Before the sweep the tree had one nanoid@3.3.12, and GHSA pingdotgg#115/pingdotgg#116 against it sat auto-dismissed by GitHub's auto-triage rule — it was scoped as a development dependency. astro 7.2.0 restructured its tree, adding an already-fixed nanoid@3.3.17 alongside the old copy and flipping that copy's scope to runtime, which took it out from under the rule and reopened both alerts.

The sweep did not introduce a vulnerability — 3.3.12 was there before and was always affected — but it turned a suppressed finding into a live one.

One override, "nanoid@3": ^3.3.17, dedupes onto the version already in the tree. Both advisories want <= 3.3.17, so this clears both.

Ledger figures refreshed in the same commit per SEAMS.md's self-reference rule.
radroid added a commit to radroid/t3code that referenced this pull request Aug 10, 2026
…eep reopened

Fallout from #56, caught by re-checking the alert list after it merged rather than assuming the count only goes down.

Before the sweep the tree had one nanoid@3.3.12, and GHSA pingdotgg#115/pingdotgg#116 against it sat auto-dismissed by GitHub's auto-triage rule — it was scoped as a development dependency. astro 7.2.0 restructured its tree, adding an already-fixed nanoid@3.3.17 alongside the old copy and flipping that copy's scope to runtime, which took it out from under the rule and reopened both alerts.

The sweep did not introduce a vulnerability — 3.3.12 was there before and was always affected — but it turned a suppressed finding into a live one.

One override, "nanoid@3": ^3.3.17, dedupes onto the version already in the tree. Both advisories want <= 3.3.17, so this clears both.

Ledger figures refreshed in the same commit per SEAMS.md's self-reference rule.
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.

1 participant