Skip to content

feat: run add-dependency installs in a PTY#3167

Merged
wwwillchen merged 7 commits intomainfrom
pty-add-dependency-runner
Apr 8, 2026
Merged

feat: run add-dependency installs in a PTY#3167
wwwillchen merged 7 commits intomainfrom
pty-add-dependency-runner

Conversation

@keppo-bot
Copy link
Copy Markdown
Contributor

@keppo-bot keppo-bot bot commented Apr 8, 2026

Summary

  • replace add-dependency command execution with a cross-platform node-pty runner in the Electron main process
  • preserve add-dependency output and failure handling while adding PTY output normalization and command timeouts
  • package node-pty correctly in Electron Forge by externalizing it from Vite, rebuilding it, and unpacking its helper binaries

Test plan

  • npm run fmt
  • npm run lint:fix
  • npm run ts
  • npm test
  • npm run build

πŸ€– Generated with Claude Code


Open with Devin

@keppo-bot keppo-bot bot requested a review from a team April 8, 2026 07:12
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 8 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

cubic-dev-ai[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

βœ… Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

dyad-assistant[bot]

This comment was marked as resolved.

@dyad-assistant
Copy link
Copy Markdown
Contributor

dyad-assistant bot commented Apr 8, 2026

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE β€” several MEDIUM issues worth addressing, no clear blockers

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard.

Issues Summary

Severity File Issue
🟑 MEDIUM pty_command_runner.ts:170 onExit ignores signal field β€” signal-killed processes can be reported as success
🟑 MEDIUM pty_command_runner.ts:6 Single 10-min wall-clock timeout serves probes and long installs poorly
🟑 MEDIUM pty_command_runner.ts:199 Timeout error message exposes raw 600000ms to end users
🟑 MEDIUM pty_command_runner.ts:83 \r collapse loses progress context on failure
🟑 MEDIUM socket_firewall.ts:86 stderr is now permanently empty β€” displaySummary regression + dead infrastructure

Why "NOT SURE" rather than "Ready to merge"

The packaging changes (forge.config.ts, vite.main.config.mts, rules/native-modules.md) are clean and well-documented β€” that part is solid. The new pty_command_runner.ts has a thoughtful settle()/dispose pattern and decent unit coverage.

What's holding back a green light is that the stderr β†’ stdout merge ripples through code that wasn't updated in this PR. Most importantly:

  • executeAddDependency.ts:60 computes displaySummary = getFirstNonEmptyLine(displayDetails) β€” and that helper now sees the full interleaved PTY stream instead of an isolated stderr verdict. The first non-empty line of a pnpm add PTY stream is overwhelmingly likely to be progress noise (Progress: resolved 12, reused 0, …) or a deprecation warning rather than the real failure reason. The detail card still has the full output, but the prominent error banner regresses.
  • executeAddDependency.ts:73–78 still does installResults: stdout + (stderr ? \\n${stderr}` : "")` β€” dead branch.
  • getCommandExecutionDisplayDetails still prefers error.stderr.trim() first β€” unreachable in production.
  • The existing executeAddDependency.test.ts / response_processor.test.ts mocks still populate stderr: "Socket Firewall blocked …", so they pass without exercising the new combined-stream reality.

None of these are crash-or-data-loss issues, but together they make me uncertain the add-dependency error UX still works the way the tests claim it does. Strongly recommend a manual or e2e run of npm run e2e e2e-tests/socket_firewall.spec.ts after npm run build (the new rules/e2e-testing.md note already calls this out β€” good catch) to verify the blocked-package banner still surfaces meaningfully.

🟒 Low Priority Notes (6 items)
  • Timeout-vs-onExit ordering race β€” pty_command_runner.ts:189-204: ptyProcess.kill() runs before settle(...) in the timeout path. If the underlying PTY exits synchronously from kill(), the onExit handler races in first and surfaces an exited with code … error instead of the timeout message. Cosmetic but masks the real cause when debugging. Flip the order or set a timedOut flag before killing.
  • Late-event chunks accumulation β€” pty_command_runner.ts:166-168: After settle() disposes the data subscription, any data event still buffered by node-pty will be appended to outputChunks and held alive until the closure is GC'd. Academic for short-lived add-dependency commands; would matter for long-lived sessions.
  • Test doesn't cover real kill β†’ onExit race β€” pty_command_runner.test.ts:126-145: The timeout test never emits an onExit after kill is called, so the production race above is untested.
  • ANSI_SINGLE_CHAR_PATTERN is hard to read β€” pty_command_runner.ts:10: [@-Z\\-_] parses correctly but takes a moment. The conventional [@-_] (full Fe range) is clearer.
  • PtySpawner parameter is dead plumbing β€” pty_command_runner.ts:53-64, 120: The fourth-argument injection point isn't exercised β€” the unit test uses vi.mock("node-pty") instead. Pick one strategy and drop the other.
  • Missing "why" comment on dataSubscription/exitSubscription stub initializers β€” pty_command_runner.ts:128-129: The { dispose: () => {} } stubs guard settle() against running before the real subscriptions are assigned (e.g. on a synchronous spawn failure). Worth a one-line comment so a future refactor doesn't delete them.
🚫 Dropped False Positives / Out-of-scope (1 item)
  • No streaming feedback during long installs β€” Dropped: The UX agent flagged that runPtyCommand buffers all output until exit, so the dyad-add-dependency card stays empty during a multi-minute install. This is a real UX gap and a missed opportunity for the PTY migration, but it would require new wiring through the IPC layer and is properly a follow-up feature, not a regression introduced by this PR.

Generated by Dyadbot multi-agent code review

@github-actions github-actions bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 407 4 6 131
πŸͺŸ Windows 406 9 5 131

Summary: 813 passed, 13 failed, 11 flaky, 262 skipped

Failed Tests

🍎 macOS

  • queued_message.spec.ts > editing queued message restores attachments and selected components
    • Error: expect(locator).toBeVisible() failed
  • queued_message.spec.ts > canceling queued message edit clears restored components
    • Error: expect(locator).toBeVisible() failed
  • setup_flow.spec.ts > Setup Flow > node.js install flow
    • TimeoutError: locator.dispatchEvent: Timeout 30000ms exceeded.
  • visual_editing.spec.ts > swap image via URL
    • TimeoutError: locator.click: Timeout 30000ms exceeded.

πŸͺŸ Windows

  • context_manage.spec.ts > manage context - default
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • context_manage.spec.ts > manage context - smart context
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • context_manage.spec.ts > manage context - smart context - auto-includes only
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • context_manage.spec.ts > manage context - exclude paths
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • context_manage.spec.ts > manage context - exclude paths with smart context
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • github.spec.ts > create and sync to new repo
    • TimeoutError: page.waitForSelector: Timeout 5000ms exceeded.
  • github.spec.ts > create and sync to new repo - custom branch
    • TimeoutError: locator.click: Timeout 30000ms exceeded.
  • github.spec.ts > create and sync to existing repo
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • setup_flow.spec.ts > Setup Flow > node.js install flow
    • TimeoutError: locator.dispatchEvent: Timeout 30000ms exceeded.

πŸ“‹ Re-run Failing Tests (macOS)

Copy and paste to re-run all failing spec files locally:

npm run e2e \
  e2e-tests/queued_message.spec.ts \
  e2e-tests/setup_flow.spec.ts \
  e2e-tests/visual_editing.spec.ts

⚠️ Flaky Tests

🍎 macOS

  • chat_image_generation.spec.ts > generate image from chat - full flow (passed after 1 retry)
  • context_manage.spec.ts > manage context - smart context (passed after 1 retry)
  • git_collaboration.spec.ts > Git Collaboration > should create, switch, rename, merge, and delete branches (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • smart_context_options.spec.ts > switching smart context mode saves the right setting (passed after 2 retries)
  • template-create-nextjs.spec.ts > create next.js app (passed after 1 retry)

πŸͺŸ Windows

  • chat_input.spec.ts > send button disabled during pending proposal - reject (passed after 1 retry)
  • chat_tabs.spec.ts > right-click context menu: Close tabs to the right (passed after 2 retries)
  • git_collaboration.spec.ts > Git Collaboration > should create, switch, rename, merge, and delete branches (passed after 2 retries)
  • github.spec.ts > create and sync to existing repo - custom branch (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)

πŸ“Š View full report

- route Windows package-manager shims through cmd.exe inside the PTY
- add signal-aware exits plus clearer timeout handling and output retention
- tighten add-dependency probe timeouts and improve combined-output summaries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

@keppo-bot
Copy link
Copy Markdown
Contributor Author

keppo-bot bot commented Apr 8, 2026

πŸ€– Claude Code Review Summary

PR Confidence: 5/5

All trusted review threads are resolved, the review-fix patch is pushed, and the branch passes formatting, lint, type-checks, unit tests, packaging, and the targeted socket-firewall E2E spec.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
Windows package-manager shims inside the PTY Wrapped Windows .cmd package-manager launches through cmd.exe inside the PTY so the add-dependency path stays PTY-backed on Windows without relying on direct batch execution. View
Signal exits and timeout handling Signal-terminated PTY processes now fail instead of resolving, probe commands use shorter explicit timeouts, and timeout errors are human-readable. Per Principle #4: Transparent Over Magical, the failure path should surface actionable status instead of opaque transport details. View
Failure-path output retention and summary extraction Failure output now preserves carriage-return frames, and add-dependency summaries prefer the most relevant error line from combined PTY output instead of progress noise. Per Principle #4: Transparent Over Magical, the prominent error banner should show what actually failed. View
Socket-firewall E2E docs command nit Verified the existing npm run e2e e2e-tests/socket_firewall.spec.ts form already works with this repo's npm script forwarding, so I replied and resolved without changing the docs. View
Product Principle Suggestions

No suggestions


πŸ€– Generated by Claude Code

@dyad-assistant
Copy link
Copy Markdown
Contributor

dyad-assistant bot commented Apr 8, 2026

πŸ” Dyadbot Code Review Summary

Verdict: β›” NO β€” HIGH-severity UX regression on the timeout path

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard.

This is a follow-up review after the latest round of fixes. The previous multi-agent review's five MEDIUM findings have been addressed well β€” the onExit signal check, the split 30s probe / 10min install timeouts, formatDuration, preserveCarriageReturnFrames, and the bottom-up DISPLAY_SUMMARY_PATTERNS heuristic are all in place. One HIGH-severity issue was introduced by the fix itself, and a few more MEDIUM items remain.

Issues Summary

Severity File Issue
πŸ”΄ HIGH pty_command_runner.ts:257 Timeout reason is hidden whenever the PTY printed anything before hanging
🟑 MEDIUM executeAddDependency.ts:33-41 DISPLAY_SUMMARY_PATTERNS misses npm ERR! / E<CODE> / ERR_PNPM_* conventions
🟑 MEDIUM socket_firewall.ts:112 Windows users see cmd.exe /d /s /c "npx.cmd" … leak into every error message
🟑 MEDIUM pty_command_runner.ts:249 Timeout kill may leave orphan node.exe children on Windows, causing cascade EPERMs

Why ":no_entry: NO" rather than "NOT SURE"

The HIGH-severity finding is the load-bearing one: on a timeout where the PTY printed anything before hanging (which is essentially every real-world timeout β€” npm/pnpm always emit early progress), the "timed out after 10 minutes. The command may be stuck…" string sits on error.message but never reaches the UI, because ExecuteAddDependencyError computes both displayDetails and displaySummary from CommandExecutionError.stdout first, and the UI only reads those two fields. The user instead sees the last Progress: resolved N, reused 0, downloaded M frame as the entire failure explanation, in both the collapsed header and the expanded detail panel. That's a strict regression from the previous child_process path, and it affects the very feature (nice timeout errors) this PR added. The fix is small β€” see the inline comment for a one-line patch to append the timeout notice onto output.

The two other MEDIUM findings are both follow-on concerns from design choices in the current fix:

  • DISPLAY_SUMMARY_PATTERNS is tuned for Socket Firewall's blocked/denied vocabulary but not for how npm and pnpm themselves format failures. npm ERR!, ERR_PNPM_FETCH_404, ENOTFOUND, ELIFECYCLE, and gyp ERR! all fail the /\berror\b/i word boundary test, so non-sfw npm failures fall through to lines.at(-1) β€” which for npm is almost always npm ERR! A complete log of this run can be found in: …/_logs/…log. Users hitting a genuine network / auth / version-conflict error see that log-path line as their entire error banner. The fix is additive: add patterns for npm ERR!, ERR_PNPM_, ELIFECYCLE, /\bE[A-Z]{3,}\b/, and filter out the "A complete log of this run" noise line before the fallback.

  • The Windows cmd.exe wrapping plumbing leaks into user-facing error messages. buildPtyInvocation returns the wrapped form, and runPtyCommand's displayedCommand is built from whatever it receives, so Windows error cards read Command 'cmd.exe /d /s /c "npx.cmd" "--prefer-offline" "--yes" "sfw@2.0.4" "pnpm" "add" "react"' exited with code 1 β€” a string that consumes most of the 100-char DyadOutput header preview before any real diagnostics land. Clean fix: thread a displayCommand option down so the unwrapped ${command} ${args.join(" ")} is what users see on every platform. The same Windows wrapping also raises a real operational concern on ptyProcess.kill() β€” killing the cmd.exe wrapper isn't guaranteed to tear down the full child-process tree under ConPTY, and orphan node.exe processes holding file locks on node_modules can turn a timeout into a cascade of follow-on EPERM failures on subsequent installs.

Still worth noting (not inline)

🟒 Low priority / code health notes (6 items)
  • Potential leaked setTimeout on synchronous exit β€” pty_command_runner.ts:224-264: timeoutId = setTimeout(...) runs after ptyProcess.onExit(...) is subscribed. If node-pty were ever to dispatch an exit synchronously during subscription, settle() would run with timeoutId === undefined, then control returns to the executor and still registers a now-useless timer. In practice node-pty dispatches asynchronously so this is latent, but the ordering is fragile β€” either register the timer before the subscriptions or guard the assignment with if (!didSettle).
  • Missing "why" comment on the { dispose: () => {} } stub initializers β€” pty_command_runner.ts:182-183: these exist so settle() stays safe if a spawn failure rejects before the real subscriptions are assigned. One-line comment would save the next refactor from deleting them.
  • runPtyCommand's 4th ptySpawner parameter is dead plumbing β€” pty_command_runner.ts:170-174: the tests use vi.mock("node-pty") instead of passing a custom spawner, and no production caller overrides the default. Pick one DI strategy and drop the other; the exported PtySpawner type becomes dead once removed.
  • ANSI_SINGLE_CHAR_PATTERN = /\u001B[@-Z\-_]/g β€” pty_command_runner.ts:10: this character class is narrower than the conventional Fe range [@-_] (it misses [, \, ], ^). In practice the CSI and OSC patterns already cover \u001B[ and \u001B], but the intent would be clearer with the full range and a comment.
  • Dead stderr field on CommandExecutionResult / CommandExecutionError β€” previously flagged in the earlier review. Tests were updated to use stdout, but CommandExecutionError.stderr still exists as a type field, getCommandExecutionDisplayDetails still checks error.stderr.trim() first (unreachable in production), and executeAddDependency.ts:99 still computes stdout + (stderr ? \\n${stderr}` : "")(dead branch). Rename to a singleoutput` field or delete the stderr plumbing outright β€” leaving it half-removed is more confusing than either option.
  • DISPLAY_SUMMARY_PATTERNS lives in executeAddDependency.ts but parses generic PTY command output β€” consider colocating it with pty_command_runner.ts (or a small command_output_summary.ts) so future PTY callers can reuse it instead of re-deriving a heuristic.
🚫 Dropped / out-of-scope (2 items)
  • No streaming progress feedback during the 10-minute install β€” Still a real UX gap (the card sits silent while pnpm add chugs along), but the UX agent and I agree with the previous review's call: this is a follow-up feature, not a regression introduced by this PR. Leave as-is and file a follow-up.
  • Timeout message's actionable hint gets truncated in the 100-char DyadOutput header β€” The UX agent flagged that the "Check your network or environment and try again" hint disappears behind the … in the collapsed card. True, but expanding the card shows the full message, so users aren't fully blocked. More importantly, this issue becomes moot if you fix the HIGH-severity finding above by restructuring the timeout flow β€” at which point it's worth revisiting whether the hint should be in displaySummary at all.

Generated by Dyadbot multi-agent code review

dyad-assistant[bot]

This comment was marked as resolved.

- surface PTY timeout reasons in captured output
- keep Windows error display text on the original command
- improve npm and pnpm failure summary selection
- kill Windows PTY process trees on timeout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@keppo-bot keppo-bot bot removed the needs-human:review-issue ai agent flagged an issue that requires human review label Apr 8, 2026
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

@keppo-bot
Copy link
Copy Markdown
Contributor Author

keppo-bot bot commented Apr 8, 2026

πŸ€– Claude Code Review Summary

PR Confidence: 5/5

The remaining trusted review feedback is addressed, the branch was pushed, and validation passed across lint, type-checking, unit tests, build packaging, and the real socket-firewall E2E.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
Timeout reason disappeared after partial PTY output Appended the timeout notice into captured PTY output so add-dependency surfaces the timeout in both details and summary, and added a unit test for the emit-output-then-timeout path. View
npm/pnpm failure summaries fell back to progress or log-path noise Expanded summary matching for npm and pnpm failure conventions, filtered known noise lines, and added focused transcript tests for npm ERR! ERESOLVE and ERR_PNPM_FETCH_404. View
Windows leaked the internal cmd.exe PTY wrapper in user-facing errors Added a PTY displayCommand override so execution can stay wrapped while user-visible errors keep the original command text. View
Windows timeout cleanup could orphan child installer processes The timeout path now uses taskkill /F /T /PID when the PTY exposes a pid, with fallback to ptyProcess.kill() and unit coverage for the Windows path. View
Product Principle Suggestions

None.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid β€” if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/ipc/utils/pty_command_runner.ts">

<violation number="1" location="src/ipc/utils/pty_command_runner.ts:168">
P2: Handle taskkill failure after spawn (non‑zero exit) so the PTY process is still killed on Windows timeouts. As written, only spawn errors trigger the fallback, leaving hung child processes if taskkill runs but fails.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

windowsHide: true,
},
);
taskkillProcess.once("error", () => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 8, 2026

Choose a reason for hiding this comment

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

P2: Handle taskkill failure after spawn (non‑zero exit) so the PTY process is still killed on Windows timeouts. As written, only spawn errors trigger the fallback, leaving hung child processes if taskkill runs but fails.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At src/ipc/utils/pty_command_runner.ts, line 168:

<comment>Handle taskkill failure after spawn (non‑zero exit) so the PTY process is still killed on Windows timeouts. As written, only spawn errors trigger the fallback, leaving hung child processes if taskkill runs but fails.</comment>

<file context>
@@ -122,6 +150,38 @@ function buildExitMessage(
+          windowsHide: true,
+        },
+      );
+      taskkillProcess.once("error", () => {
+        try {
+          ptyProcess.kill();
</file context>
Fix with Cubic

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@dyad-assistant dyad-assistant bot left a comment

Choose a reason for hiding this comment

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

Multi-agent review: 3 medium issues found around error-message output hygiene.

displayedCommand: string,
timeoutMs: number,
): string {
return `Command '${displayedCommand}' timed out after ${formatDuration(timeoutMs)}. The command may be stuck. Check your network or environment and try again.`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟑 MEDIUM | microcopy

Timeout guidance is cut off in the collapsed error card

The new timeout message is helpful but long. For pnpm add react, displaySummary becomes:

Command 'pnpm add react' timed out after 10 minutes. The command may be stuck. Check your network or environment and try again.

response_processor.ts:243 prepends Failed to add dependencies: react. , and DyadOutput.tsx:61 truncates the collapsed card title at 100 characters. For a typical install, the user sees ...timed out after 10 minutes. The comman... with the remediation hint cut off. For a longer package name like @typescript-eslint/eslint-plugin, the truncation eats the timed out phrase entirely β€” the user just sees a repeated command prefix and has to click to expand before they understand the command timed out.

πŸ’‘ Suggestion: Front-load the timeout duration so the actionable info fits in the first ~100 characters even after the Failed to add dependencies: <pkgs>. prefix, e.g. Timed out after 10 minutes running 'pnpm add react'. Check your network or try again.

reject(
new PtyCommandExecutionError({
message: buildExitMessage(displayedCommand, exitCode, signal),
output,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟑 MEDIUM | error-messaging

Signal-killed installs surface a progress line as the user-visible summary β€” the signal reason is dropped

When the PTY exits due to a signal (SIGKILL from OOM, user Ctrl+C, Electron shutting the renderer), this branch rejects with a descriptive message (Command 'pnpm add react' was terminated by signal 15) but passes the raw output through unchanged β€” unlike the timeout branch above, which appends its timeoutMessage to output via appendCommandMessage.

Downstream, getCommandExecutionDisplayDetails prefers any non-empty stdout, so ExecuteAddDependencyError.displayDetails becomes the raw captured PTY output. If that output is just pnpm's TTY progress frames (very common when pnpm is killed mid-resolve), getDisplaySummary walks backwards: no DISPLAY_SUMMARY_PATTERNS line matches, every line matches the ^progress: noise filter, so the function falls through to lines.at(-1) and returns a progress line. The user sees a red error card titled something like Failed to add dependencies: react. Progress: resolved 50, reused 0, downloaded 0, added 0 with no hint that the process was killed. The informative was terminated by signal N exists on error.message but never reaches displaySummary.

πŸ’‘ Suggestion: Append buildExitMessage(...) to output on the failure path the same way buildTimeoutMessage is appended in the timeout branch, so both displayDetails and (transitively) displaySummary carry the real reason. Example:

const exitMessage = buildExitMessage(displayedCommand, exitCode, signal);
const finalOutput = appendCommandMessage(output, exitMessage);
settle(() => reject(new PtyCommandExecutionError({ message: exitMessage, output: finalOutput, exitCode, signal })));

@dyad-assistant
Copy link
Copy Markdown
Contributor

dyad-assistant bot commented Apr 8, 2026

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard. No HIGH issues. Three MEDIUM issues cluster around a single theme: the user-facing error output is still leaky in ways the new heuristics don't catch.

Issues Summary

Severity File Issue
🟑 MEDIUM src/ipc/utils/pty_command_runner.ts:99 Timeout guidance is cut off in the collapsed DyadOutput card (~100 char truncate)
🟑 MEDIUM src/ipc/utils/pty_command_runner.ts:300 Signal-killed installs surface a Progress: ... line instead of the signal reason
🟑 MEDIUM src/ipc/utils/pty_command_runner.ts:288 Expanded error details are now walls of pnpm/npm progress-bar noise

All three are filed as inline comments on the PR.

🟒 Low Priority Notes (10 items)

Correctness & edge cases

  • .cmd auto-suffix on any bare Windows command β€” src/ipc/utils/socket_firewall.ts:67-75. resolveExecutableName('foo', 'win32') returns 'foo.cmd' unconditionally. Current callers (npm/pnpm/npx) are safe, but the helper is a latent trap for future callers whose shim isn't a .cmd (e.g. git, node).
  • quoteWindowsCmdArg does not neutralize cmd.exe metacharacters β€” src/ipc/utils/socket_firewall.ts:77-108. cmd.exe still expands %VAR% inside double quotes, and there's no way to disable expansion in /c mode. Unlikely to bite npm package specs but worth documenting the assumption.
  • Backspace handler removes a UTF-16 code unit, not a code point β€” src/ipc/utils/pty_command_runner.ts:208-211. currentLine.slice(0, -1) can leave a lone surrogate if the prior character was a supplementary code point (emoji/CJK extension).
  • runCommand drops signal when translating PtyCommandExecutionError β†’ CommandExecutionError β€” src/ipc/utils/socket_firewall.ts:132-139. The signal field carried by the PTY error is lost. Callers only observe throw/no-throw today so behavior is unchanged, but debugging info is silently discarded.

Code health

  • Duplicate withPlatform helper across two test files β€” src/ipc/utils/pty_command_runner.test.ts:86-104 and src/ipc/utils/socket_firewall.test.ts:33-51. Byte-identical copies; extract into a shared test helper.
  • Duplicate buildCommandDisplay / buildDisplayedCommand helpers β€” src/ipc/utils/socket_firewall.ts:31-33 and src/ipc/utils/pty_command_runner.ts:91-93. Same one-line body, near-identical name, and every production call goes through both copies.
  • ADD_DEPENDENCY_INSTALL_TIMEOUT_MS is a bare alias of the default β€” src/ipc/utils/socket_firewall.ts:18. Passed explicitly in executeAddDependency.ts:120 but would resolve to the same value if omitted. The re-export also lives in socket_firewall.ts even though it has nothing to do with Socket Firewall.
  • PTY geometry and default-timeout magic numbers lack rationale β€” src/ipc/utils/pty_command_runner.ts:4-7. cols=160, rows=24, and 10 minutes are tunables a future reader will want to justify.
  • child_process mock factory uses an obscure default-export fallback β€” src/ipc/utils/pty_command_runner.test.ts:14-31. Production code only uses the named spawn import, so the ('default' in actual ? ...) gymnastics can likely be simplified.
  • runCommand β†’ runPtyCommand β†’ node-pty pyramid β€” src/ipc/utils/pty_command_runner.ts:230-333 / socket_firewall.ts:127-160. Middle layer mostly renames output β†’ {stdout, stderr:""} and swaps error classes. Worth flattening once the already-flagged dead stderr field is removed.
🚫 Dropped / Not Flagged (1 item)
  • DISPLAY_SUMMARY_PATTERNS is an overlapping regex soup β€” Dropped. The overlap (\bfailed\b vs \berror\b, \btimed out\b vs \btimeout\b) is deliberate: each pattern catches a distinct real-world output format, and the behavior is covered by focused tests in executeAddDependency.test.ts. Subjective complexity concern, not a real maintainability bug.

Generated by Dyadbot multi-agent code review

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

- filter PTY progress noise out of add-dependency error details
- fall back to the error message when only noisy PTY frames remain
- keep response-processor coverage aligned with filtered error cards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: 2bacec8274

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +175 to 179
await runner("npx", [...SOCKET_FIREWALL_NPX_ARGS, "--help"], {
timeoutMs: SOCKET_FIREWALL_PROBE_TIMEOUT_MS,
});
return { available: true };
} catch {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail closed when Socket Firewall probe times out

The new 30s probe timeout is handled by the same catch path as a true "sfw not installed" failure, so a slow first-time npx fetch now marks Socket Firewall unavailable and allows dependency installs to continue without blocking unsafe packages. This creates a fail-open path for users with slower/unstable networks even when unsafe-package blocking is enabled; treat timeout/transient probe errors differently (retry or abort install) instead of silently downgrading protection.

Useful? React with πŸ‘Β / πŸ‘Ž.

@dyad-assistant
Copy link
Copy Markdown
Contributor

dyad-assistant bot commented Apr 8, 2026

πŸ” Dyadbot Code Review Summary

Verdict: βœ… YES - Ready to merge

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard. This PR has been through multiple prior review rounds, and the current HEAD addresses nearly all raised concerns. No new HIGH or MEDIUM blockers found.

Issues Summary

No new HIGH or MEDIUM issues. A handful of LOW polish items below β€” none blocking.

🟒 Low Priority Notes (12 items)

Code clarity

  • getDisplaySummary two-pass + lines.at(-1) fallback lacks a comment β€” src/ipc/processors/executeAddDependency.ts:77-98. Two independent reviewers flagged the layered fallback as confusing. A one-line comment explaining the "prefer pattern-matched, then any non-noise, then last line" intent would save a debugging session.
  • PTY "failed" determination has no rationale comment β€” src/ipc/utils/pty_command_runner.ts:286. A future reader could "simplify" exitCode !== 0 || hasSignal(signal) back to exitCode !== 0 and silently break the SIGTERM-after-timeout path.
  • Magic PTY geometry DEFAULT_PTY_COLS = 160, DEFAULT_PTY_ROWS = 24 β€” src/ipc/utils/pty_command_runner.ts:5-6. These affect how pnpm/npm wrap progress output, which feeds into noise matching. Worth a one-line // wide enough to avoid pnpm progress wrapping comment.

Duplication / abstraction

  • buildDisplayedCommand (pty_command_runner.ts:91) and buildCommandDisplay (socket_firewall.ts:31) are the same one-liner with different names in sibling files. Pick one and export it.
  • runCommand is now a thin translation layer from PtyCommandExecutionError (output) to CommandExecutionError (stdout/dead stderr). Two parallel error types represent the same thing. Consider collapsing, or at least document why both exist.

Edge cases

  • Windows noise pattern misses UNC paths β€” src/ipc/processors/executeAddDependency.ts:51. /^npm err!\s*(?:[A-Za-z]:\\|\/).+/i matches C:\... and /... but not \\server\share\.npm\_logs\.... On domain-joined Windows machines, the UNC log path can leak into the displayed summary.
  • quoteWindowsCmdArg doesn't double trailing backslashes β€” src/ipc/utils/socket_firewall.ts:77-79. An arg ending in \ becomes \" inside the quoted form, which can merge into the next token. Not reachable today (package-name args are sanitized upstream), but runCommand is generic and may be called with user-controlled paths later.
  • normalizePtyOutput can leak ANSI-body chars when a process is killed mid-escape β€” src/ipc/utils/pty_command_runner.ts:185-228. Stray [33m fragments can appear in timeout-path output. Cosmetic, not blocking.

Test gap

  • No unit test for the taskkill spawn-error fallback β€” src/ipc/utils/pty_command_runner.test.ts. The success path is covered, but the once('error', ...) β†’ ptyProcess.kill() fallback isn't. Combined with the still-open P2 concern about non-zero taskkill exit, the fallback is invisible to regression tests.

Microcopy / UX polish (mostly pre-existing, out of scope)

  • "Fix with AI" button may be misleading for install timeouts β€” src/components/chat/DyadOutput.tsx:83-91. This PR introduces a new 10-minute timeout error class, and the existing card shows a prominent "Fix with AI" button. A network timeout isn't fixable by the model. Consider hiding the button for timeout errors, or offering "Retry install" instead.
  • 10-minute install has no visible progress β€” src/components/chat/DyadAddDependency.tsx. The card is static while the install runs; users on slow networks may assume Dyad is frozen. Opportunity to stream the last non-noise PTY line as a live progress indicator.
  • "exited with code 1" / "terminated by signal 15" leak to the card header. Consider mapping common exit scenarios to plain English (e.g., signal 15 β†’ "cancelled").
  • ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/react: Not Found surfaces untranslated as the summary. A small mapper (e.g., ERR_PNPM_FETCH_404 β†’ "Package not found on the npm registry") would be friendlier, but the raw code is still informative enough to act on.
🚫 Dropped False Positives / Duplicates (8 items)
  • Race condition in timeout handler β€” Dropped: Node.js is single-threaded; clearTimeout reliably cancels pending timers. No practical race.
  • Trailing PTY output can be dropped after dispose() β€” Dropped: too narrow/theoretical; node-pty flushes before onExit in practice.
  • hasSignal misses string signals β€” Dropped: based on an incorrect assumption about node-pty v1's signal type (it's numeric).
  • CommandExecutionResult.stderr always empty and dead ternary in runAddDependencyCommand β€” Dropped as a duplicate of an existing unresolved comment (prior review already flagged this). Still worth addressing, but the existing comment covers it.
  • Timeout message truncated by 100-char card header β€” Dropped as a duplicate of existing prior-round comment.
  • Stripping npm warn deprecated loses debugging hints on failure β€” Dropped: debatable product decision; the PR explicitly tests the deprecation-stripping behavior as intentional noise reduction.

Generated by Dyadbot multi-agent code review

@wwwillchen wwwillchen merged commit 49d415d into main Apr 8, 2026
16 of 23 checks passed
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