Skip to content

fix(run-tab): surface PTY startup failures and tighten run UI - #222

Merged
arul28 merged 3 commits into
mainfrom
ade/run-tab-fixes-59abaa92
May 1, 2026
Merged

fix(run-tab): surface PTY startup failures and tighten run UI#222
arul28 merged 3 commits into
mainfrom
ade/run-tab-fixes-59abaa92

Conversation

@arul28

@arul28arul28 commented Apr 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Surface PTY startup failures in the run transcript so users see what failed instead of a silent empty card
  • Tighten run-tab UI: CommandCard, RunPage, and ChatTerminalDrawer behavior cleanups
  • Add tests covering PTY startup failure paths and advanced drawer behavior

Test plan

  • Unit tests pass (processService, ptyService, RunPage advanced drawer)
  • Manually run a command that fails to spawn — error is written to transcript
  • Manually verify run tab UI still works for normal commands

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a "Terminal" button to command cards for quick access to live process terminals.
    • Introduced a dedicated terminal drawer for improved terminal interface management.
  • Bug Fixes

    • Enhanced error reporting for process startup failures with detailed diagnostic logs.
    • Added shell execution fallback for improved command compatibility on non-Windows systems.

Greptile Summary

This PR surfaces PTY startup failures into the run transcript (with diagnostic details like command, cwd, and error message), adds a shell-exec fallback for non-Windows systems when direct PTY spawn fails, and replaces the inline shell-session list in RunPage with the shared ChatTerminalDrawer. A new "Terminal" button on CommandCard lets users open a live terminal for any running process.

  • P1 — disposeTabsOnUnmount=true disposes process-backed PTYs: When a user clicks the "Terminal" button on a CommandCard, the run-command's PTY is added to the drawer's tab list. On RunPage unmount the cleanup disposes every tab indiscriminately, which kills live run-command processes (e.g. npm run dev) that should persist in the background. Only interactive shell tabs created via "New shell" need cleanup.
  • P2 — tracked flag silently changed: The old handleLaunchShell used tracked: false; ChatTerminalDrawer.createTab always uses tracked: true. If this is intentional, a note in the code would help.

Confidence Score: 3/5

Not safe to merge as-is — navigating away from RunPage with an open run-command terminal will kill the underlying process.

One P1 defect: disposeTabsOnUnmount=true disposes all drawer tabs on unmount, including PTYs that back live run-command processes. Navigating away from RunPage after clicking Terminal on a running command silently kills it. The rest of the changes are well-structured with good test coverage.

apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx — the disposeTabsOnUnmount cleanup effect and the createTab tracked value both need review.

Important Files Changed

FilenameOverview
apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsxAdds autoCreateOnOpen, createRequestNonce, disposeTabsOnUnmount, emptyMessage, and onCreateError props. disposeTabsOnUnmount=true disposes ALL tabs on unmount, including process-backed PTYs revealed from run commands — this can kill live processes on navigation.
apps/desktop/src/renderer/components/run/RunPage.tsxReplaces inline shell session list with ChatTerminalDrawer. Adds revealRuntimeTerminal, upsertRuntime, and handleOpenRuntimeTerminal. The disposeTabsOnUnmount prop correctly cleans up shells on unmount, but also disposes run-command PTYs.
apps/desktop/src/main/services/processes/processService.tsAdds cwd to handleStartFailure and writes a diagnostic block to the transcript on PTY startup failures. Best-effort write is correctly wrapped in try/catch.
apps/desktop/src/main/services/pty/ptyService.tsAdds quotePosixShellArg and buildDirectCommandShellFallback. When direct-command spawn fails on non-Windows, falls back to spawning a shell and sending exec as a startup command. Logic is sound.
apps/desktop/src/renderer/components/run/CommandCard.tsxAdds optional onOpenRuntime prop; renders a Terminal button when the latest runtime has both sessionId and ptyId. Clean, safe addition.
apps/desktop/src/main/services/processes/processService.test.tsAdds test covering PTY startup failure writing to transcript. Test structure mirrors existing patterns and assertions are thorough.
apps/desktop/src/main/services/pty/ptyService.test.tsAdds test for shell fallback when direct command spawn fails. Verifies both the failed direct spawn call and the subsequent shell spawn with exec startup command.
apps/desktop/src/renderer/components/run/RunPage.advancedDrawer.test.tsxExpands drawer test suite with scenarios: shared terminal drawer for new shells, plain toggle without auto-create, shell creation failure surfacing, unmount disposal, and runtime terminal reveal. Coverage is solid.

Sequence Diagram

sequenceDiagram
participant User
participant RunPage
participant ChatTerminalDrawer
participant processService
participant ptyService
User->>RunPage: Click Run on CommandCard
RunPage->>processService: start({ laneId, processId })
processService->>ptyService: create(...)
alt PTY spawn succeeds
ptyService-->>processService: { ptyId, sessionId }
processService-->>RunPage: ProcessRuntime { ptyId, sessionId }
RunPage->>ChatTerminalDrawer: revealRequest (ptyId, sessionId)
ChatTerminalDrawer->>ChatTerminalDrawer: addTab(ptyId) — now tracked by drawer
else PTY spawn fails
ptyService->>ptyService: buildDirectCommandShellFallback()
ptyService->>ptyService: spawn shell + exec cmd
ptyService-->>processService: throws Error
processService->>processService: handleStartFailure → write to transcript
processService-->>RunPage: throws Error
RunPage->>RunPage: setActionError
end
User->>RunPage: Navigate away (unmount)
RunPage->>ChatTerminalDrawer: unmount (disposeTabsOnUnmount=true)
ChatTerminalDrawer->>ptyService: dispose(all tabs) — kills process-backed PTYs too
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx, line 359-364 (link)

    P1disposeTabsOnUnmount kills process-backed PTYs alongside interactive shells

    When a user clicks the "Terminal" button on a CommandCard, revealRuntimeTerminal adds a tab with the run command's ptyId to this drawer. With disposeTabsOnUnmount=true, all tabs are disposed on RunPage unmount — including those attached to live run-command processes. Disposing a process-backed PTY signals the process manager that the PTY has exited, which marks the process as crashed. A user who opens npm run dev in the terminal drawer and then navigates away would silently kill the process.

    Only interactive shell tabs (those created via "New shell") should be disposed on unmount; tabs revealed from existing run-command PTYs should be left alone. One approach: add a source: "shell" | "process" field to TabEntry so the cleanup can skip process-backed tabs:

    if(!disposeTabsOnUnmount)return;for(consttaboftabsRef.current){if(tab.source==="process")continue;// let processService manage its own PTYswindow.ade.pty.dispose({ptyId: tab.ptyId,sessionId: tab.sessionId}).catch(()=>{});}
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx
    Line: 359-364
    Comment:
    **`disposeTabsOnUnmount` kills process-backed PTYs alongside interactive shells**
    When a user clicks the "Terminal" button on a `CommandCard`, `revealRuntimeTerminal` adds a tab with the run command's `ptyId` to this drawer. With `disposeTabsOnUnmount=true`, **all** tabs are disposed on `RunPage` unmount — including those attached to live run-command processes. Disposing a process-backed PTY signals the process manager that the PTY has exited, which marks the process as `crashed`. A user who opens `npm run dev` in the terminal drawer and then navigates away would silently kill the process.
    Only interactive shell tabs (those created via "New shell") should be disposed on unmount; tabs revealed from existing run-command PTYs should be left alone. One approach: add a `source: "shell" | "process"` field to `TabEntry` so the cleanup can skip process-backed tabs:
    ```typescriptif (!disposeTabsOnUnmount) return;
    for (const tab oftabsRef.current) {
    if (tab.source==="process") continue; // let processService manage its own PTYswindow.ade.pty.dispose({ ptyId: tab.ptyId, sessionId: tab.sessionId }).catch(() => {});
    }
    ```
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---### Issue 1 of 2
apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx:359-364
**`disposeTabsOnUnmount` kills process-backed PTYs alongside interactive shells**
When a user clicks the "Terminal" button on a `CommandCard`, `revealRuntimeTerminal` adds a tab with the run command's `ptyId` to this drawer. With `disposeTabsOnUnmount=true`, **all** tabs are disposed on `RunPage` unmount — including those attached to live run-command processes. Disposing a process-backed PTY signals the process manager that the PTY has exited, which marks the process as `crashed`. A user who opens `npm run dev` in the terminal drawer and then navigates away would silently kill the process.
Only interactive shell tabs (those created via "New shell") should be disposed on unmount; tabs revealed from existing run-command PTYs should be left alone. One approach: add a `source: "shell" | "process"` field to `TabEntry` so the cleanup can skip process-backed tabs:
```typescriptif (!disposeTabsOnUnmount) return;
for (const tab oftabsRef.current) {
if (tab.source==="process") continue; // let processService manage its own PTYswindow.ade.pty.dispose({ ptyId: tab.ptyId, sessionId: tab.sessionId }).catch(() => {});
}
```### Issue 2 of 2
apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx:221-229
**`tracked: true` silently changes shell session behavior from previous implementation**
The old `handleLaunchShell` in `RunPage` explicitly created shells with `tracked: false`. `ChatTerminalDrawer.createTab` always uses `tracked: true`. Run-tab shells are now tracked, which may attach them to session history or other tracked-session flows (e.g., the `terminal.list` restore path that guards on `chatSessionId`). If "tracked" implies inclusion in session-level persistence or IPC broadcasts that weren't expected for ephemeral run-tab shells, this could produce side effects. If this change is intentional, a comment noting the deliberate switch from `false` to `true` would help reviewers.

Reviews (3): Last reviewed commit: "ship: checkpoint before merging main int..." | Re-trigger Greptile

@vercel

vercelBot commented Apr 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
adeIgnoredIgnoredPreviewMay 1, 2026 1:01am

@coderabbitai

coderabbitaiBot commented Apr 30, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@arul28 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 46 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea949e99-6364-4a4b-a4e8-105d169ba62a

📥 Commits

Reviewing files that changed from the base of the PR and between 4616257 and d625dac.

📒 Files selected for processing (4)
  • apps/desktop/src/main/services/processes/processService.test.ts
  • apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx
  • apps/desktop/src/renderer/components/run/RunPage.advancedDrawer.test.tsx
  • apps/desktop/src/renderer/components/run/RunPage.tsx
📝 Walkthrough

Walkthrough

The changes introduce startup-failure diagnostics for PTY-backed processes, a shell-exec fallback for failed direct command spawns, and terminal integration in the Run page. On the backend: processService captures working directory and errors to transcript files; ptyService attempts shell-fallback execution with POSIX quoting when direct spawns fail. On the frontend: ChatTerminalDrawer gains creation-request nonces and customizable empty states; CommandCard and RunPage add runtime terminal access; RunPage refactors shell-session handling to use the drawer with pending-launch tracking.

Changes

Cohort / File(s)Summary
Process Service Layer
apps/desktop/src/main/services/processes/processService.ts, apps/desktop/src/main/services/processes/processService.test.ts
Adds structured failure reporting to transcript files with process identifier, command, working directory, and error details; introduces test coverage validating startup-failure handling and crash status recording.
PTY Service Layer
apps/desktop/src/main/services/pty/ptyService.ts, apps/desktop/src/main/services/pty/ptyService.test.ts
Implements shell-exec fallback with POSIX quoting for failed direct command spawns on non-Windows platforms; adds test coverage for shell candidate spawning and PTY write invocation.
Terminal Drawer Component
apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx
Adds optional createRequestNonce and emptyMessage props; introduces request-handling tracking refs and conditional PTY-exit subscriptions guarded by tab availability.
Run Page Components
apps/desktop/src/renderer/components/run/CommandCard.tsx, apps/desktop/src/renderer/components/run/RunPage.tsx, apps/desktop/src/renderer/components/run/RunPage.advancedDrawer.test.tsx
CommandCard: adds optional onOpenRuntime callback and conditional Terminal button rendering. RunPage: removes legacy shell-session tracking, refactors runtime state via upsertRuntime helper with pending-launch reconciliation, integrates ChatTerminalDrawer with drawer-driven shell creation. Test: enhances ptyService mocking and adds test cases for shell creation and process runtime launching.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title 'fix(run-tab): surface PTY startup failures and tighten run UI' accurately summarizes the main changes: adding PTY startup failure visibility and UI refinements to the run tab.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/run-tab-fixes-59abaa92

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
Review rate limit: 0/1 reviews remaining, refill in 8 minutes and 46 seconds.

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

Comment threadapps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx Outdated
Comment threadapps/desktop/src/main/services/pty/ptyService.ts Outdated
- Write PTY startup errors into the run transcript so failed commands are visible
- Improve CommandCard, RunPage, and ChatTerminalDrawer behavior for run-tab fixes
- Add tests covering PTY failure paths and advanced drawer behavior
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
arul28force-pushed the ade/run-tab-fixes-59abaa92 branch from dcaa110 to a927190CompareMay 1, 2026 00:08
…ner active
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28

Copy link
Copy Markdown
OwnerAuthor

@codex review

@coderabbitaicoderabbitaiBot 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 (2)
apps/desktop/src/main/services/processes/processService.test.ts (1)

1071-1078: ⚡ Quick win

Assert the working-directory line to fully lock the new failure contract.

This test already validates command + error text; add one assertion for the emitted cwd line so the new cwd propagation behavior stays protected.

🔍 Suggested assertion
 expect(tail).toContain("failed to start");
expect(tail).toContain("./missing-script.sh dev");
+ expect(tail).toContain(`[ADE] Working directory: ${tmpDir}`);
expect(tail).toContain("spawn ./missing-script.sh ENOENT");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/main/services/processes/processService.test.ts` around lines
1071 - 1078, Add an assertion to the existing test block that verifies the
working-directory line is present in the emitted log tail to lock the new
failure contract; specifically, after calling service.getLogTail(...) (the tail
variable) add an expectation that the tail contains the emitted
cwd/working-directory line produced by the process failure (so the test
alongside checking "failed to start", the command string, and the spawn ENOENT
message also asserts the working-directory propagation string emitted by the
code that logs the cwd).
apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx (1)

358-370: 💤 Low value

Potential subscription churn on tab count changes.

The onExit effect re-runs on every tabs.length change, which means unsubscribing and resubscribing with each tab add/remove. During this brief window, an exit event could be missed.

Consider using a stable dependency (e.g., a boolean derived from tabs.length > 0) or moving the guard inside the subscription callback:

♻️ Suggested approach
 useEffect(() => {
- if (tabs.length === 0) return undefined;
const ptyBridge = window.ade?.pty;
if (!ptyBridge?.onExit) return undefined;
const unsubscribe = ptyBridge.onExit((ev: PtyExitEvent) => {
setTabs((prev) => prev.map((tab) => (
tab.ptyId === ev.ptyId
? { ...tab, exited: true }
: tab
)));
});
return unsubscribe;
-}, [tabs.length]);+}, []);

The setTabs callback already handles the case where prev is empty (it just returns an empty array), so guarding inside the effect isn't strictly necessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx` around
lines 358 - 370, The effect currently re-subscribes on every change to
tabs.length which risks missing events; update the useEffect in
ChatTerminalDrawer.tsx to use a stable dependency (e.g., Boolean(tabs.length)
instead of tabs.length) or remove tabs.length from the dependency array and
perform the empty-tabs guard inside the subscription callback; specifically
adjust the useEffect that references ptyBridge, ptyBridge.onExit, and setTabs
(and the PtyExitEvent handler) so subscriptions are not torn down/recreated on
each tab add/remove and the setTabs callback continues to handle empty prev
state.
🤖 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/desktop/src/main/services/processes/processService.test.ts`:
- Around line 1071-1078: Add an assertion to the existing test block that
verifies the working-directory line is present in the emitted log tail to lock
the new failure contract; specifically, after calling service.getLogTail(...)
(the tail variable) add an expectation that the tail contains the emitted
cwd/working-directory line produced by the process failure (so the test
alongside checking "failed to start", the command string, and the spawn ENOENT
message also asserts the working-directory propagation string emitted by the
code that logs the cwd).
In `@apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx`:
- Around line 358-370: The effect currently re-subscribes on every change to
tabs.length which risks missing events; update the useEffect in
ChatTerminalDrawer.tsx to use a stable dependency (e.g., Boolean(tabs.length)
instead of tabs.length) or remove tabs.length from the dependency array and
perform the empty-tabs guard inside the subscription callback; specifically
adjust the useEffect that references ptyBridge, ptyBridge.onExit, and setTabs
(and the PtyExitEvent handler) so subscriptions are not torn down/recreated on
each tab add/remove and the setTabs callback continues to handle empty prev
state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a6b4d75f-be16-41e0-822a-0f05c4df0165

📥 Commits

Reviewing files that changed from the base of the PR and between 399fbd0 and 4616257.

📒 Files selected for processing (8)
  • apps/desktop/src/main/services/processes/processService.test.ts
  • apps/desktop/src/main/services/processes/processService.ts
  • apps/desktop/src/main/services/pty/ptyService.test.ts
  • apps/desktop/src/main/services/pty/ptyService.ts
  • apps/desktop/src/renderer/components/chat/ChatTerminalDrawer.tsx
  • apps/desktop/src/renderer/components/run/CommandCard.tsx
  • apps/desktop/src/renderer/components/run/RunPage.advancedDrawer.test.tsx
  • apps/desktop/src/renderer/components/run/RunPage.tsx

Comment threadapps/desktop/src/renderer/components/run/RunPage.tsx

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:4616257738

ℹ️ 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".

Comment threadapps/desktop/src/renderer/components/run/RunPage.tsx
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@arul28
arul28 merged commit 1133169 into mainMay 1, 2026
1 of 2 checks passed
@arul28
arul28 deleted the ade/run-tab-fixes-59abaa92 branch May 8, 2026 03:23
@coderabbitaicoderabbitaiBot mentioned this pull request May 14, 2026
Sign up for freeto 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

@arul28