Skip to content

fix: prevent macOS task hang — force-close connections, improve process cleanup#423

Merged
Astro-Han merged 18 commits into
Astro-Han:devfrom
Spongeacer:fix/macos-task-hang
May 4, 2026
Merged

fix: prevent macOS task hang — force-close connections, improve process cleanup#423
Astro-Han merged 18 commits into
Astro-Han:devfrom
Spongeacer:fix/macos-task-hang

Conversation

@Spongeacer

@Spongeacer Spongeacer commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

修复 macOS 上任务完成后 app 卡死的问题,并把后续 review 里暴露出的清理路径补齐:

  1. killSidecar()close=true — 强制关闭 SSE/WebSocket 连接,防止 Electron 进程带着半死不活的服务器挂住
  2. Processor cleanup 使用 bounded 1s 等待 — 只等待 Bash/PTY 正常落盘截断输出和退出状态;进程生命周期仍在 Bash/PTY 层显式终止
  3. Bash/PTY 共用 Process.terminateTree() — 先尝试 process group,再 fallback 到 root + descendants,TERM 后统一 500ms grace 再 KILL;Windows 使用 taskkill /f /t
  4. LLM 增加 silent-stream timeoutSILENT_STREAM_TIMEOUT_MS 只限制两次 provider event 之间的静默时间,不是总运行时长
  5. 重试策略加 10 次上限 — 防止无限重试循环
  6. 补齐 out-of-order tool event cleanup — 保留 queued tool updates 和 diagnostics,避免 tool-call 早于 tool-input-start 时丢 metadata

Related Issues

Testing

  • bun test test/util/process.test.ts test/pty/pty-session.test.ts test/tool/bash.test.ts test/session/prompt-effect.test.ts --timeout 30000
  • bun test test/session/llm.test.ts test/session/prompt-effect.test.ts --timeout 30000
  • bun --cwd packages/opencode typecheck, bun --cwd packages/sdk/js typecheck
  • CI: unit-opencode, typecheck, unit-app, unit-desktop, smoke-macos-arm64, e2e-artifacts, analyze-js-ts, CodeQL
  • macOS local coverage: Bash abort/timeout, PTY child cleanup, Process.spawn abort tree cleanup, Process.stop shared cleanup, SDK stop tree cleanup, pgrep fallback, silent LLM stream timeout, retry max attempts

Summary by CodeRabbit

  • Bug Fixes

    • More reliable embedded sidecar shutdown and improved session teardown so processes and subscriber connections stop cleanly
    • Safer staged process termination (graceful then forced) ensuring descendant/tree cleanup and preventing lingering processes
  • Improvements

    • Configurable inactivity timeout for LLM streaming to cancel stalled provider streams (default 10 minutes)
    • Added a 10-attempt retry cap for session retries
    • More robust processor handling: queued tool-part updates and stronger synthetic stop/fail behavior
  • Tests

    • New/extended tests for PTY/session termination, process-tree termination, abort behavior, and silent-stream timeouts

…e process cleanup

- killSidecar: pass close=true to force-close SSE/WebSocket connections on shutdown
- processor: increase toolcall cleanup timeout from 250ms to 5s for child process teardown
- pty: send explicit SIGTERM signal instead of undefined for reliable process termination
- llm: add 10-minute stream timeout to prevent session stuck in busy state on network stall
- retry: cap retry attempts at 10 to prevent infinite retry loops

Fixes Astro-Han#421, Fixes Astro-Han#422
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c87ddf7c-b657-4395-a6f8-06ec9a8beaca

📥 Commits

Reviewing files that changed from the base of the PR and between c01d67f and ae7dd03.

📒 Files selected for processing (1)
  • packages/sdk/js/src/process.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/js/src/process.ts

📝 Walkthrough

Walkthrough

Force-closes the desktop sidecar connections; implements staged, cross-platform process-tree termination and helpers to detect/process descendants; reworks PTY teardown to track async cleanup and publish ordered events; adds an inactivity timeout for LLM streaming and a retry-attempt cap; buffers and applies pending processor tool updates/actions; and updates tests covering these behaviors.

Changes

Shutdown, PTY & Session Cleanup

Layer / File(s) Summary
Constants & Data Shape
packages/opencode/src/pty/index.ts, packages/opencode/src/session/llm.ts, packages/opencode/src/session/retry.ts, packages/opencode/src/session/processor.ts
Adds EXIT_WAIT_MS, SILENT_STREAM_TIMEOUT_MS, RETRY_MAX_ATTEMPTS, TOOL_CLEANUP_TIMEOUT_MS; extends Active session with exitCode?; adds cleanupTasks set; adds streamTimeoutMs?: number; and introduces pendingToolUpdates / pendingLoopActions.
Core process utilities / Termination
packages/opencode/src/util/process.ts, packages/sdk/js/src/process.ts
Adds Process.TERMINATION_GRACE_MS, Process.exists, Process.descendants, and Process.terminateTree(...); reimplements Process.stop (and SDK stop) to use process-tree termination and descendant enumeration with grace/escalation.
PTY teardown & lifecycle
packages/opencode/src/pty/index.ts
Reworks teardown to close subscribers, add waitForExit/hasExited/signalProcess/terminate helpers (using Process.terminateTree), track async cleanupTasks, await settled cleanup in finalizer, and set session exitCode.
Event publishing & remove flow
packages/opencode/src/pty/index.ts
Pty.remove awaits teardown(session) for exit code, conditionally publishes pty.exited (default -1 if missing) when not already published, then publishes pty.deleted; proc.onExit marks exit and schedules tracked cleanup that publishes pty.exited and calls remove(id).
Tool termination wiring
packages/opencode/src/tool/bash.ts
On abort and timeout branches, replaces timed handle.kill({... forceKillAfter ...}) with Process.terminateTree({ pid: handle.pid, waitForExit: Effect.runPromise(handle.exitCode) }).
LLM stream inactivity timeout
packages/opencode/src/session/llm.ts
Exports SILENT_STREAM_TIMEOUT_MS, adds optional streamTimeoutMs, wraps provider streams with a resettable inactivity timer that aborts on silence, normalizes stream items into Error objects, and applies Stream.timeout(...).
Processor pending updates/actions & cleanup
packages/opencode/src/session/processor.ts
Adds pendingToolUpdates and pendingLoopActions; updateToolCall queues updates when part missing; adds applyPendingToolUpdates; failToolCall consumes pending loop actions and merges diagnostics; recordSyntheticBlock/recordSyntheticStop buffer actions until parts exist; increases tool-call cleanup wait to TOOL_CLEANUP_TIMEOUT_MS.
Sidecar shutdown change
packages/desktop-electron/src/main/index.ts
killSidecar() now calls server.stop(true) to force-close active connections.
Tests / Test helpers
packages/opencode/test/..., packages/desktop-electron/...
Adds/adjusts tests: PTY event order and child-termination; bash abort test for signal-ignoring trees; silent-stream timeout helper and test; retry-policy max-attempts test; terminateTree and Process.stop tests; desktop-sidecar test asserts server.stop(true).

Sequence Diagram(s)

sequenceDiagram
  participant App as rgba(52,160,52,0.5)
  participant Sidecar as rgba(66,135,245,0.5)
  participant PTY as rgba(245,166,35,0.5)
  participant ChildProc as rgba(168,88,214,0.5)
  participant LLM as rgba(220,57,18,0.5)

  App->>Sidecar: killSidecar() → server.stop(true)
  Sidecar-->>App: closeAllConnections (SSE/WebSocket closed)

  App->>PTY: Pty.remove(id)
  PTY->>PTY: closeSubscribers(session)
  PTY->>ChildProc: terminateTree(SIGTERM → waitForExit)
  PTY->>PTY: wait EXIT_WAIT_MS / TERMINATION_GRACE_MS
  alt still alive after grace
    PTY->>ChildProc: escalate SIGKILL (group/individual)
  end
  PTY-->>App: publish pty.exited → pty.deleted

  App->>LLM: stream(..., streamTimeoutMs)
  LLM->>App: stream events (reset inactivity timer)
  alt no events within timeout
    LLM-->>App: abort provider response body / cancel stream
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I nudged the sidecar to shut the gate,
Sent TERM then KILL when sprouts would not wait,
Timed silent streams so stalled talks unwind,
Netted stray PIDs and tidied what I'd find,
Hop—cleanup finished, now the app can celebrate.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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 name Status Explanation
Title check ✅ Passed Title 'fix: prevent macOS task hang — force-close connections, improve process cleanup' clearly and specifically summarizes the main changes: forcing connection closure and improving process cleanup on macOS.
Description check ✅ Passed Description includes all key template sections: Summary, Why (Related Issues), verification steps with results, and relevant risk callouts for macOS/platform impacts.
Linked Issues check ✅ Passed All coding requirements from issues #421 and #422 are met: killSidecar(true), bounded processor cleanup, Process.terminateTree() shared by Bash/PTY, LLM silent-stream timeout, retry max attempts, and out-of-order tool event handling.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issues: process cleanup, connection closure, timeout handling, and tool event ordering. No unrelated refactors or dependency changes introduced.

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

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

Review rate limit: 8/10 reviews remaining, refill in 10 minutes and 28 seconds.

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces several stability and timeout improvements across the codebase, including explicit signal handling for process termination, increased timeouts for tool calls, and a 10-minute timeout for LLM streams. It also implements a maximum retry limit of 10 attempts. A critical issue was identified in the retry logic where an incorrect method was used to terminate the retry schedule, which should be corrected to ensure the schedule stops as intended.

Comment thread packages/opencode/src/session/retry.ts
@Astro-Han

Copy link
Copy Markdown
Owner

补充一下 maintainer update:我在这个 PR 上追加了一个提交 9d8b4cab0 fix: terminate pty process trees,把进程清理从单纯延长等待时间改成真正终止 PTY 进程树。

这次主要做了三件事:

  1. Pty.remove() 现在会先关闭订阅连接,再在 macOS/Linux 上对整个 process group 发 SIGTERM,短暂等待后再发 SIGKILL。如果 process group 不可用,会回退到原来的单进程 kill。这样可以覆盖 npm runmakecargo 等命令继续留下子进程的情况。
  2. Pty.remove() 会等待 PTY exit 事件或短超时后再发布 deleted,避免修复进程清理时打乱原有 created -> exited -> deleted 事件顺序。
  3. 增加了回归测试:
    • PTY 中启动会忽略 HUP/TERM 的子进程,Pty.remove() 后必须被清掉。
    • BashTool abort 后也不会遗留忽略 TERM 的子进程,确认 tool 层现有 process group kill 没有回归。

本地验证已通过:

bun --cwd packages/opencode test test/pty/pty-session.test.ts test/tool/bash.test.ts --timeout 20000
bun --cwd packages/opencode typecheck

这部分是对 #421 / #422 根因修复的补强,不再只是增加 cleanup/stream/retry 的等待时间。

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/pty/index.ts`:
- Around line 183-206: The finalizer currently calls the sync path
(teardownSync/terminateSync) which schedules a detached SIGKILL via
setTimeout.unref(); change the finalizer to be effectful and await the async
teardown path by invoking the Effect-based teardown(session) (and
terminate(session) as used inside it) instead of teardownSync, ensuring the
finalizer yields until the child processes are torn down; update any other
finalizer occurrences (also around the 216-220 area) to use Effect.addFinalizer
or Effect.acquireRelease inside InstanceState.make and call/await
teardown(session) rather than the fire-and-forget teardownSync/terminateSync
helpers.
- Around line 164-196: The terminate and terminateSync functions send signals
even when a session has already exited, risking PID/PGID reuse races; update
terminate and terminateSync (referencing terminate, terminateSync, waitForExit,
signalProcess, signalGroup, session.info.status, sleep, and
TERMINATION_GRACE_MS) to short-circuit immediately if session.info.status ===
"exited" and to re-check liveness after the grace period before escalating to
SIGKILL (e.g., re-check waitForExit or verify the PID/PGID is still alive),
skipping any further signals if the session became exited in the meantime.

In `@packages/opencode/test/tool/bash.test.ts`:
- Line 1095: Update the test command so the background child installs its own
signal handlers before running sleep: instead of relying on the parent trap in
the command string `trap '' HUP TERM; sleep 30 & echo $! >
${JSON.stringify(pidFile)}; echo before; wait`, spawn the background job via a
subshell that runs its own `trap '' HUP TERM` (or at least `trap '' TERM`) then
writes its PID to `pidFile` and runs `sleep 30`, ensuring the child process
ignores TERM independently of the parent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: db4a2e14-e79f-4489-b6c6-5b01a193b454

📥 Commits

Reviewing files that changed from the base of the PR and between 77b6d8b and 9d8b4ca.

📒 Files selected for processing (3)
  • packages/opencode/src/pty/index.ts
  • packages/opencode/test/pty/pty-session.test.ts
  • packages/opencode/test/tool/bash.test.ts

Comment thread packages/opencode/src/pty/index.ts Outdated
Comment thread packages/opencode/src/pty/index.ts Outdated
Comment thread packages/opencode/test/tool/bash.test.ts Outdated
@Astro-Han

Copy link
Copy Markdown
Owner

Update pushed in 4494673.

What changed:

  • PTY cleanup now skips already-exited sessions, awaits teardown in the InstanceState finalizer, and terminates the precise child process tree with a bounded TERM/KILL path instead of detached cleanup.
  • BashTool abort/timeout now terminates the spawned process tree directly, so child processes that ignore TERM are still cleaned up.
  • Fixed the unit-opencode prompt-effect failures by preserving pending tool metadata and pending loop-gate block/stop decisions when the AI SDK executes tools before emitting the tool-call part.
  • Restored processor cleanup waiting to the short bounded path instead of extending it to 5s.
  • Strengthened the BashTool and PTY regression tests so the background child installs its own signal handler.

Verified locally:

  • bun test test/pty/pty-session.test.ts test/tool/bash.test.ts --timeout 30000
  • bun test test/session/prompt-effect.test.ts --timeout 30000
  • bun --cwd packages/opencode typecheck
  • git diff --check

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
packages/opencode/test/pty/pty-session.test.ts (1)

98-135: Use testEffect(...)/it.live(...) for this PTY Effect-service test.

This test exercises Pty Effect services and depends on real child-process behavior and git; it should use the testEffect + it.live pattern. Additionally, replace the Instance.provide(...) pattern with provideTmpdirInstance(...) for better cleanup and Effect composition alignment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/pty/pty-session.test.ts` around lines 98 - 135, The
test "remove terminates child processes started inside the pty" should be
converted to use the Effect-based test helpers: replace the top-level test(...)
with testEffect(...) and make the body an it.live(...) spec so it runs live
against real processes; swap the Instance.provide({ directory: dir.path, fn:
async () => { ... } }) usage for provideTmpdirInstance(...) to obtain the
tmpdir-backed Instance within the Effect environment, and keep the same logic
that calls Pty.create(...) and Pty.remove(...) (ensure id is tracked and removed
in finally). Ensure the effect returns/awaits the same waits and assertions
inside the live effect so cleanup and process isolation occur under the
testEffect/it.live lifecycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/opencode/test/pty/pty-session.test.ts`:
- Around line 98-135: The test "remove terminates child processes started inside
the pty" should be converted to use the Effect-based test helpers: replace the
top-level test(...) with testEffect(...) and make the body an it.live(...) spec
so it runs live against real processes; swap the Instance.provide({ directory:
dir.path, fn: async () => { ... } }) usage for provideTmpdirInstance(...) to
obtain the tmpdir-backed Instance within the Effect environment, and keep the
same logic that calls Pty.create(...) and Pty.remove(...) (ensure id is tracked
and removed in finally). Ensure the effect returns/awaits the same waits and
assertions inside the live effect so cleanup and process isolation occur under
the testEffect/it.live lifecycle.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b05f00d1-87d6-4d55-86fb-7c8f4d20eeb0

📥 Commits

Reviewing files that changed from the base of the PR and between 4494673 and 84f6bc0.

📒 Files selected for processing (1)
  • packages/opencode/test/pty/pty-session.test.ts

@Astro-Han

Copy link
Copy Markdown
Owner

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/session/processor.ts`:
- Around line 227-241: The applyPendingToolUpdates function currently deletes
ctx.pendingToolUpdates[toolCallID] before verifying the part exists, which loses
updates when readToolCall(toolCallID) returns undefined; change the flow in
applyPendingToolUpdates so you only delete ctx.pendingToolUpdates[toolCallID]
after a successful match and successful session.updatePart(part) (i.e., move the
delete to after the if (!match) check and after the part is created/updated),
ensuring readToolCall and session.updatePart complete before clearing the
pending queue and then update ctx.toolcalls accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 277a6c0d-f5be-4ce8-ac72-d00ea81d1ccb

📥 Commits

Reviewing files that changed from the base of the PR and between f296398 and e65fa19.

📒 Files selected for processing (8)
  • packages/desktop-electron/src/main/index.ts
  • packages/opencode/src/pty/index.ts
  • packages/opencode/src/session/llm.ts
  • packages/opencode/src/session/processor.ts
  • packages/opencode/src/session/retry.ts
  • packages/opencode/src/tool/bash.ts
  • packages/opencode/test/pty/pty-session.test.ts
  • packages/opencode/test/tool/bash.test.ts

Comment thread packages/opencode/src/session/processor.ts
@Astro-Han Astro-Han added bug Something isn't working P1 High priority harness Model harness, prompts, tool descriptions, and session mechanics platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions labels May 4, 2026
@Astro-Han

Copy link
Copy Markdown
Owner

Follow-up update for the latest review pass:

  • Added PR labels: bug, P1, desktop, and harness.
  • Reworked PTY onExit cleanup so the auto-remove path is now tracked and awaited by instance finalization instead of being a detached runFork cleanup.
  • Kept PTY termination on the precise process tree path from the previous update; the older process-group/5s fallback comments no longer match this head.
  • Made the LLM stream timeout explicit as a silent-stream timeout (SILENT_STREAM_TIMEOUT_MS) and wired it to abort the provider request if the stream goes quiet.
  • Added regression coverage for auto PTY cleanup after short-lived process exit, retry max attempts, silent stream cancellation, and desktop sidecar force-close behavior.

Local verification:

  • bun test test/pty/pty-session.test.ts test/session/retry.test.ts test/session/llm.test.ts test/tool/bash.test.ts --timeout 30000
  • bun test src/main/index-sidecar-source.test.ts --timeout 30000
  • bun --cwd packages/opencode typecheck

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/opencode/test/session/retry.test.ts (1)

125-150: ⚡ Quick win

Use testEffect/it.effect for this Effect workflow test.

This new case is Effect-driven (Effect.runPromiseExit, Effect.retry) but is still written with plain test(...). Please move it to the Effect test harness for consistency with the test conventions in this package.

As per coding guidelines: “Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows. Use it.effect(...) when the test should run with TestClock and TestConsole.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/retry.test.ts` around lines 125 - 150, Replace
the plain Jest test(...) with the Effect test harness by converting the test
titled "policy stops retrying after the configured max attempts" to use
testEffect (from test/lib/effect.ts); instead of calling Effect.runPromiseExit
imperatively inside the test body, return the Effect workflow to the harness
(i.e., make the test function return the Effect that runs the retry logic and
asserts via Exit/expect), keeping the same Effect pipeline (Effect.try +
Effect.retry with SessionRetry.policy and references to
SessionRetry.RETRY_MAX_ATTEMPTS and attempts array) so the harness manages
execution and TestClock/TestConsole as required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/pty/index.ts`:
- Around line 138-148: waitForExit currently resolves when either the process
actually exits or when EXIT_WAIT_MS elapses, which can cause a raced publish of
pty.deleted before the true onExit later publishes pty.exited; change
waitForExit (and the other similar call sites) to keep the timeout branch
separate from the real exit signal: attach the onExit handler and return a
promise that only resolves on the actual onExit, but also return or set a
boolean (e.g., timedOut) when the timeout fires so the caller can proceed with
deletion without treating the timeout as a real exit; additionally ensure the
global onExit handler (the session.process.onExit subscriber used elsewhere)
checks session.info.status or the timedOut flag and unsubscribes/disables its
publish of pty.exited if a prior timeout-driven deletion has already occurred,
and always dispose the temporary subscription created by waitForExit when either
branch wins.

In `@packages/opencode/src/session/llm.ts`:
- Line 404: The assignment to timeoutMs uses caller-controlled
input.streamTimeoutMs directly; guard it so 0, negative, or NaN values fall back
to the default SILENT_STREAM_TIMEOUT_MS. Replace the expression around const
timeoutMs = input.streamTimeoutMs ?? SILENT_STREAM_TIMEOUT_MS with a validation
that checks Number.isFinite(input.streamTimeoutMs) and input.streamTimeoutMs > 0
(or Math.max with a defined minimum) and otherwise assigns
SILENT_STREAM_TIMEOUT_MS, referencing the timeoutMs variable and
input.streamTimeoutMs in the fix.

---

Nitpick comments:
In `@packages/opencode/test/session/retry.test.ts`:
- Around line 125-150: Replace the plain Jest test(...) with the Effect test
harness by converting the test titled "policy stops retrying after the
configured max attempts" to use testEffect (from test/lib/effect.ts); instead of
calling Effect.runPromiseExit imperatively inside the test body, return the
Effect workflow to the harness (i.e., make the test function return the Effect
that runs the retry logic and asserts via Exit/expect), keeping the same Effect
pipeline (Effect.try + Effect.retry with SessionRetry.policy and references to
SessionRetry.RETRY_MAX_ATTEMPTS and attempts array) so the harness manages
execution and TestClock/TestConsole as required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a05d6956-24f4-4c96-aab1-a2ed12ee4e76

📥 Commits

Reviewing files that changed from the base of the PR and between e65fa19 and 440238e.

📒 Files selected for processing (6)
  • packages/desktop-electron/src/main/index-sidecar-source.test.ts
  • packages/opencode/src/pty/index.ts
  • packages/opencode/src/session/llm.ts
  • packages/opencode/test/pty/pty-session.test.ts
  • packages/opencode/test/session/llm.test.ts
  • packages/opencode/test/session/retry.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/test/pty/pty-session.test.ts

Comment thread packages/opencode/src/pty/index.ts
Comment thread packages/opencode/src/session/llm.ts Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/session/processor.ts`:
- Around line 623-624: The queued running-state update in updateToolCall leaves
running undefined and skips SessionDiagnostics.observeToolCall when tool-call
precedes tool-input-start; modify updateToolCall (and the out-of-order handling
at the same block around lines 636-652) to either (a) record/serialize the
diagnostics metadata (the same data written by
SessionDiagnostics.observeToolCall, including diagnostics.loop) into the
pending-update structure so applyPendingToolUpdates will replay both state and
diagnostics, or (b) call SessionDiagnostics.observeToolCall when queuing so the
diagnostics write is executed immediately; ensure applyPendingToolUpdates also
applies any queued diagnostics write when it replays pending updates so
loopRecords, errorRecords and completeToolCall see the same metadata as the
in-order path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bd9371d9-19ba-4065-aaac-dc4d370fbd72

📥 Commits

Reviewing files that changed from the base of the PR and between 440238e and 1d71120.

📒 Files selected for processing (1)
  • packages/opencode/src/session/processor.ts

Comment thread packages/opencode/src/session/processor.ts

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/opencode/test/util/process.test.ts (1)

63-74: ⚡ Quick win

This regression test doesn't validate tree cleanup yet.

The spawned script has no child process, so a root-only kill would still make this pass. To lock in the /T behavior, make the parent spawn a long-lived child and assert that the child PID is gone after terminateTree(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/util/process.test.ts` around lines 63 - 74, The test
currently spawns a single process so it cannot verify tree-wide cleanup; update
the test named "terminateTree uses the platform process-tree cleanup path on
Windows" so the spawned parent process (created via Process.spawn) itself spawns
a long-lived child, capture that child's PID from the parent (or have the parent
print it and parse proc.stdout), call Process.terminateTree({ pid: proc.pid! }),
then assert the child's PID is no longer running (e.g., wait for the child to
exit or check that the child PID cannot be found) rather than only checking
proc.exited; this ensures the `/T` tree-kill behavior is asserted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/util/process.ts`:
- Around line 241-248: The TERM grace period is skipped when input.waitForExit
is not provided; change the rootExited calculation so that even if
input.waitForExit is undefined we still wait graceMs before escalating.
Specifically, update the rootExited logic (where input.waitForExit,
sleep(graceMs) and graceMs are referenced) to either race
input.waitForExit.then(() => true) against sleep(graceMs).then(() => false) when
present, or await sleep(graceMs) and set rootExited = false when
input.waitForExit is absent, so that the later groupSignaled && !rootExited &&
exists(input.pid) branch honors the grace period before calling
signalGroup(input.pid, "SIGKILL").

---

Nitpick comments:
In `@packages/opencode/test/util/process.test.ts`:
- Around line 63-74: The test currently spawns a single process so it cannot
verify tree-wide cleanup; update the test named "terminateTree uses the platform
process-tree cleanup path on Windows" so the spawned parent process (created via
Process.spawn) itself spawns a long-lived child, capture that child's PID from
the parent (or have the parent print it and parse proc.stdout), call
Process.terminateTree({ pid: proc.pid! }), then assert the child's PID is no
longer running (e.g., wait for the child to exit or check that the child PID
cannot be found) rather than only checking proc.exited; this ensures the `/T`
tree-kill behavior is asserted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f5968b70-02f0-47a0-b9c8-1a87930526dd

📥 Commits

Reviewing files that changed from the base of the PR and between df2388a and 3637c99.

📒 Files selected for processing (4)
  • packages/opencode/src/pty/index.ts
  • packages/opencode/src/tool/bash.ts
  • packages/opencode/src/util/process.ts
  • packages/opencode/test/util/process.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/src/tool/bash.ts

Comment thread packages/opencode/src/util/process.ts Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/util/process.ts`:
- Around line 258-279: The group-kill branch currently calls
signalGroup(input.pid, "SIGKILL") and returns without touching the
previously-enumerated children, which can leave descendants orphaned; update the
branch in the function containing signalGroup, signalPid, children, exists, and
input.waitForExit so that after signalGroup(input.pid, "SIGKILL") you also
iterate over the children array and call signalPid(child, "SIGKILL") (optionally
checking exists(child) before signaling), emit a debug log similar to other
paths, and then await the same Promise.race([input.waitForExit.catch(() =>
undefined), sleep(graceMs)]) before returning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fd32a75-b7d5-4851-8b98-670474d8fa37

📥 Commits

Reviewing files that changed from the base of the PR and between 83fd511 and 6a2406d.

📒 Files selected for processing (4)
  • packages/opencode/src/pty/index.ts
  • packages/opencode/src/tool/bash.ts
  • packages/opencode/src/util/process.ts
  • packages/opencode/test/util/process.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/opencode/src/tool/bash.ts
  • packages/opencode/src/pty/index.ts

Comment thread packages/opencode/src/util/process.ts

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/sdk/js/src/process.ts`:
- Around line 39-43: The shutdown code sends SIGTERM and SIGKILL immediately,
preventing graceful shutdown; modify the sequence in the block that calls
descendants(proc.pid) and signal to first send SIGTERM to proc.pid and each
child, then wait a short grace window (e.g., 500ms — same as packages/opencode
behavior) before sending SIGKILL to proc.pid and each child; implement the wait
with a Promise-based sleep (or setTimeout wrapped in a Promise) so the function
can await the delay and then proceed to send SIGKILL only after the grace
period.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ef3f44f1-60fb-498f-a9f8-ec3b26f68af5

📥 Commits

Reviewing files that changed from the base of the PR and between 27dab7a and afb1263.

📒 Files selected for processing (3)
  • packages/opencode/src/util/process.ts
  • packages/opencode/test/util/process.test.ts
  • packages/sdk/js/src/process.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/opencode/test/util/process.test.ts
  • packages/opencode/src/util/process.ts

Comment thread packages/sdk/js/src/process.ts Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/sdk/js/src/process.ts`:
- Around line 39-46: The delayed SIGKILL timer can fire after the child already
exited and its PID recycled; modify the shutdown logic around
descendants(proc.pid) / signal(proc.pid, ...) so that after creating killTimer
you register handlers on the child process (proc.on('exit'), proc.on('close')
and proc.on('error')) that clearTimeout(killTimer) (and call killTimer.unref if
needed) and remove those handlers so the timer is cancelled if the process exits
or errors before the 500ms window; keep the existing SIGTERM/SIGKILL signaling
and ensure you reference the existing symbols (proc.pid, descendants, signal,
killTimer) when adding/clearing the timer and listeners.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e4611246-5e82-4c24-b239-54bae7a2d1dd

📥 Commits

Reviewing files that changed from the base of the PR and between afb1263 and c01d67f.

📒 Files selected for processing (1)
  • packages/sdk/js/src/process.ts

Comment thread packages/sdk/js/src/process.ts
@Astro-Han Astro-Han merged commit 92ba7e9 into Astro-Han:dev May 4, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working harness Model harness, prompts, tool descriptions, and session mechanics P1 High priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions

Projects

None yet

2 participants