fix(cli,core): stop dev workers spinning at 100% CPU after parent CLI disconnect#3491
fix(cli,core): stop dev workers spinning at 100% CPU after parent CLI disconnect#3491
Conversation
… disconnect
Orphaned trigger-dev-run-worker and trigger-dev-index-worker processes were
getting stuck in an uncaughtException feedback loop when the parent CLI closed
the IPC channel: a periodic IPC send via process.send would throw
ERR_IPC_CHANNEL_CLOSED, which re-entered the same handler that itself called
process.send. The loop was amplified by source-map-support's prepareStackTrace
running every iteration.
- @trigger.dev/core: ZodIpcConnection drops packets when process.connected is
false and swallows synchronous send errors, so closed-channel sends no longer
throw out of the IPC layer.
- trigger.dev (cli-v3): dev-run-worker and dev-index-worker now exit cleanly via
process.on("disconnect") instead of being re-parented to init.
- trigger.dev (cli-v3): all four worker entry points wrap their uncaughtException
process.send calls in safeSend, which checks process.connected and swallows
synchronous throws so a closed channel can never re-enter the handler.
🦋 Changeset detectedLatest commit: 67cab6c The changes in this PR will be included in the next version bump. This PR includes changesets to release 29 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughThis pull request fixes a CPU busy-loop issue in dev workers that occurs when the parent CLI disconnects. The fix prevents IPC failures from cascading into recursive error handling by introducing a Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli-v3/src/entryPoints/dev-index-worker.ts (1)
205-212:⚠️ Potential issue | 🟠 MajorUse
safeSend(msg)instead ofprocess.send?.(msg)on line 210 for consistency and robustness.The TASKS_FAILED_TO_PARSE error handler path (line 210) directly calls
process.send?.(msg)while the INDEX_COMPLETE success path (line 201) correctly usessafeSend(msg). ThesafeSendwrapper checksprocess.connected, guards against IPC channel errors, and prevents busy-looping on a dead channel—protections that should apply to all message-sending paths, especially error handlers.Suggested fix
if (err instanceof ZodSchemaParsedError) { return sendMessageInCatalog( indexerToWorkerMessages, "TASKS_FAILED_TO_PARSE", { zodIssues: err.error.issues, tasks }, async (msg) => { - process.send?.(msg); + safeSend(msg); } ); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cli-v3/src/entryPoints/dev-index-worker.ts` around lines 205 - 212, Replace the direct IPC send call in the TASKS_FAILED_TO_PARSE handler with the existing safeSend wrapper: inside the sendMessageInCatalog call for "TASKS_FAILED_TO_PARSE" (currently using process.send?.(msg)), call safeSend(msg) instead so the error path uses the same process.connected and error-guarding logic as the INDEX_COMPLETE path; locate sendMessageInCatalog usage for "TASKS_FAILED_TO_PARSE" and swap process.send?.(msg) to safeSend(msg).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/cli-v3/src/entryPoints/dev-index-worker.ts`:
- Around line 205-212: Replace the direct IPC send call in the
TASKS_FAILED_TO_PARSE handler with the existing safeSend wrapper: inside the
sendMessageInCatalog call for "TASKS_FAILED_TO_PARSE" (currently using
process.send?.(msg)), call safeSend(msg) instead so the error path uses the same
process.connected and error-guarding logic as the INDEX_COMPLETE path; locate
sendMessageInCatalog usage for "TASKS_FAILED_TO_PARSE" and swap
process.send?.(msg) to safeSend(msg).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b3ae7520-c3f2-4506-9f16-96530545b072
📒 Files selected for processing (6)
.changeset/dev-worker-disconnect-loop.mdpackages/cli-v3/src/entryPoints/dev-index-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/core/src/v3/zodIpc.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (28)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: units / e2e-webapp / 🧪 E2E Tests: Webapp
- GitHub Check: sdk-compat / Deno Runtime
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
- GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
- GitHub Check: sdk-compat / Cloudflare Workers
- GitHub Check: sdk-compat / Bun Runtime
- GitHub Check: typecheck / typecheck
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
Files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use zod for validation in packages/core and apps/webapp
Files:
packages/core/src/v3/zodIpc.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Add crumbs as you write code using
//@Crumbscomments or `// `#region` `@crumbsblocks. These are temporary debug instrumentation and must be stripped usingagentcrumbs stripbefore merge.
Files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
packages/core/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/core/CLAUDE.md)
Never import the root package (
@trigger.dev/core). Always use subpath imports such as@trigger.dev/core/v3,@trigger.dev/core/v3/utils,@trigger.dev/core/logger, or@trigger.dev/core/schemas
Files:
packages/core/src/v3/zodIpc.ts
**/*.ts{,x}
📄 CodeRabbit inference engine (CLAUDE.md)
Always import from
@trigger.dev/sdkwhen writing Trigger.dev tasks. Never use@trigger.dev/sdk/v3or deprecatedclient.defineJob.
Files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
packages/cli-v3/src/entryPoints/**/*
📄 CodeRabbit inference engine (packages/cli-v3/CLAUDE.md)
Code in
src/entryPoints/runs inside customer containers and is a different runtime environment from the CLI - changes affect deployed task execution directly
Files:
packages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
🧠 Learnings (17)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/redis-worker/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:43.173Z
Learning: Applies to packages/redis-worker/**/redis-worker/src/worker.ts : Worker loop and job processing should implement concurrency control in src/worker.ts
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/redis-worker/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:43.173Z
Learning: Do NOT add new jobs to zodworker (internal/zodworker) or graphile-worker
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).
Applied to files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.
Applied to files:
packages/core/src/v3/zodIpc.tspackages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
📚 Learning: 2026-04-16T14:19:16.330Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-04-16T14:19:16.330Z
Learning: Applies to apps/webapp/app/v3/**Worker.server.ts : Do NOT add new jobs using zodworker/graphile-worker (legacy). Background job workers use `trigger.dev/redis-worker` via files like `app/v3/commonWorker.server.ts`, `app/v3/alertsWorker.server.ts`, `app/v3/batchTriggerWorker.server.ts`
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2026-03-26T23:24:54.682Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3114
File: apps/supervisor/src/workloadServer/index.ts:817-825
Timestamp: 2026-03-26T23:24:54.682Z
Learning: In `apps/supervisor/src/workloadServer/index.ts` (`WorkloadServer.stop()`), pending items returned by `this.snapshotDelayWheel?.stop()` are intentionally logged and dropped rather than dispatched. The entire supervisor is shutting down, so the snapshot callback URL would point at a dead server; dispatching snapshots during teardown would create orphaned gateway callbacks. Runners detect the supervisor is gone and reconnect to a new supervisor instance, which re-triggers the snapshot workflow. Do not flag the drop-on-shutdown behavior as a bug.
Applied to files:
.changeset/dev-worker-disconnect-loop.mdpackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
📚 Learning: 2026-03-02T12:43:43.173Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/redis-worker/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:43.173Z
Learning: Applies to packages/redis-worker/**/*@(job|queue|worker|background).{ts,tsx} : Use trigger.dev/redis-worker for all new background job implementations, replacing graphile-worker and zodworker
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2025-10-08T11:48:12.327Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2593
File: packages/core/src/v3/workers/warmStartClient.ts:168-170
Timestamp: 2025-10-08T11:48:12.327Z
Learning: The trigger.dev runners execute only in Node 21 and 22 environments, so modern Node.js APIs like AbortSignal.any (introduced in v20.3.0) are supported.
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/build/**/* : Bundle worker code using the build system in `src/build/` based on configuration from `trigger.config.ts`
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2026-03-25T15:29:25.889Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2026-03-25T15:29:25.889Z
Learning: Run `npx trigger.devlatest dev` to start the Trigger.dev development server
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2026-04-13T21:44:00.644Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3368
File: apps/webapp/app/services/taskIdentifierRegistry.server.ts:49-58
Timestamp: 2026-04-13T21:44:00.644Z
Learning: In `triggerdotdev/trigger.dev`, a deployment with zero tasks is not a realistic scenario in practice. Do not flag missing handling for empty-task deployments in `apps/webapp/app/services/taskIdentifierRegistry.server.ts` or related registry/sync logic.
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2026-03-22T13:26:15.187Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:15.187Z
Learning: In triggerdotdev/trigger.dev, `navigator.clipboard.writeText()` is intentionally called without awaiting or catching errors throughout the codebase (e.g., `apps/webapp/app/components/code/TextEditor.tsx`). Do not raise unhandled-promise or missing-await issues for clipboard write calls in this project.
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2026-03-02T12:43:43.173Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/redis-worker/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:43.173Z
Learning: Do NOT add new jobs to zodworker (internal/zodworker) or graphile-worker
Applied to files:
.changeset/dev-worker-disconnect-loop.md
📚 Learning: 2024-10-18T15:41:52.352Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 1418
File: packages/core/src/v3/errors.ts:364-371
Timestamp: 2024-10-18T15:41:52.352Z
Learning: In `packages/core/src/v3/errors.ts`, within the `taskRunErrorEnhancer` function, `error.message` is always defined, so it's safe to directly call `error.message.includes("SIGTERM")` without additional checks.
Applied to files:
packages/cli-v3/src/entryPoints/managed-run-worker.ts
📚 Learning: 2026-03-02T12:43:43.173Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/redis-worker/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:43.173Z
Learning: Applies to packages/redis-worker/**/redis-worker/src/worker.ts : Worker loop and job processing should implement concurrency control in src/worker.ts
Applied to files:
packages/cli-v3/src/entryPoints/managed-run-worker.tspackages/cli-v3/src/entryPoints/dev-run-worker.tspackages/cli-v3/src/entryPoints/managed-index-worker.tspackages/cli-v3/src/entryPoints/dev-index-worker.ts
📚 Learning: 2024-10-08T15:31:34.807Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 1389
File: apps/coordinator/src/index.ts:403-0
Timestamp: 2024-10-08T15:31:34.807Z
Learning: In `apps/coordinator/src/index.ts`, moving the `exitRun` and `crashRun` functions outside the `onConnection` method is not feasible because of TypeScript type issues with `socket`. Keeping these functions inside `onConnection` is preferred.
Applied to files:
packages/cli-v3/src/entryPoints/dev-run-worker.ts
📚 Learning: 2026-04-23T13:26:31.290Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3430
File: apps/webapp/app/presenters/v3/RunStreamPresenter.server.ts:0-0
Timestamp: 2026-04-23T13:26:31.290Z
Learning: In `apps/webapp/app/utils/sse.ts` (and callers such as `apps/webapp/app/presenters/v3/RunStreamPresenter.server.ts`), the string-reason type passed to `.abort()` is NOT load-bearing for heap retention in SSE streams on Node 20. Isolation testing (2000 requests × 200 KB payload) confirmed `.abort()` vs `.abort("string")` produces identical heap deltas once `AbortSignal.any` is removed. The actual root cause of the SSE memory leak was `AbortSignal.any`, which registers signals with a FinalizationRegistry that does not reliably release them on abort (see nodejs/node#54614 and `#55351`). Named sentinel constants (`ABORT_REASON_*`, exported from `sse.ts`) are a readability/style win per the CLAUDE.md named-constant guideline, not a correctness fix. Do not flag `.abort()` vs `.abort("reason string")` as a memory-retention issue in future reviews of this codebase.
Applied to files:
packages/cli-v3/src/entryPoints/dev-index-worker.ts
📚 Learning: 2026-03-26T23:23:33.560Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3114
File: apps/supervisor/src/index.ts:454-457
Timestamp: 2026-03-26T23:23:33.560Z
Learning: In `apps/supervisor/src/index.ts` (`ManagedSupervisor.stop()`), the shutdown order is intentional: `workloadServer.stop()` is called first to drain the timer wheel and stop accepting new snapshot callbacks, then `workerSession.stop()` is called to tear down the dequeue loop and runner connections (it handles its own in-flight drain). Reversing the order would leave the workloadServer up with no session to report to after runners disconnect. Do not flag this ordering as incorrect.
Applied to files:
packages/cli-v3/src/entryPoints/dev-index-worker.ts
🔇 Additional comments (6)
packages/cli-v3/src/entryPoints/dev-run-worker.ts (1)
80-127: Good IPC disconnect hardening in worker entrypoint.
process.on("disconnect")+safeSend()in theuncaughtExceptionpath cleanly addresses the re-entrant send loop risk.packages/cli-v3/src/entryPoints/managed-index-worker.ts (1)
30-63: safeSend rollout here looks consistent and complete.The guarded send path is correctly applied in both uncaught-exception reporting and message forwarding callbacks.
Also applies to: 203-213
packages/cli-v3/src/entryPoints/managed-run-worker.ts (1)
80-120: Uncaught-exception IPC reporting is safely guarded now.Using
safeSend()here closes the send-throw re-entry path without changing message shape.packages/core/src/v3/zodIpc.ts (1)
260-274:#sendPacketguard is correct for disconnected IPC channels.Early drop + wrapped
process.sendis the right protection against closed-channel sync throws.packages/cli-v3/src/entryPoints/dev-index-worker.ts (1)
30-69: Disconnect handling + safeSend integration is solid in the primary paths.These changes correctly prevent the uncaughtException IPC feedback loop in normal send flows.
Also applies to: 200-202
.changeset/dev-worker-disconnect-loop.md (1)
1-7: Changeset entry is clear and appropriately scoped.Patch bump + rationale accurately describe the worker disconnect loop fix.
Orphaned
trigger-dev-run-workerprocesses were pinning CPU at 100% after the dev CLI exited — stuck in an uncaughtException feedback loop where a closed IPC channel kept throwingERR_IPC_CHANNEL_CLOSEDback into a handler that itself calledprocess.send.Fix:
ZodIpcConnectionno-ops sends when the channel is disconnected.process.disconnectinstead of being re-parented to init.uncaughtExceptionhandlers route through asafeSendguard so the handler can never re-enter itself.Verified end-to-end:
kill -9of the dev CLI now cleans up all child workers within ~2s.