Skip to content

Add DenoChildProcessSpawner - #6688

Merged
tim-smart merged 4 commits into
mainfrom
agent/codex-engineer/eff-141-deno-child-process-spawner
Jul 28, 2026
Merged

Add DenoChildProcessSpawner#6688
tim-smart merged 4 commits into
mainfrom
agent/codex-engineer/eff-141-deno-child-process-spawner

Conversation

@tim-smart

@tim-smart tim-smart commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • extract the ChildProcessSpawner tests into a shared conformance suite
  • add a native Deno.ChildProcessSpawner backed by Deno.Command and Web Streams
  • emulate shell execution and document Deno's additional-fd, detached-process, and descendant-reaping divergences

Validation

  • deno task --filter @effect/platform-deno test --run
  • pnpm --filter @effect/platform-node-shared test --run test/NodeChildProcessSpawner.test.ts
  • deno check .
  • pnpm check
  • pnpm lint
  • pnpm exec docgen in packages/platform-deno

Closes EFF-141
Closes EFF-163
Closes EFF-164

Summary by CodeRabbit

  • New Features
    • Added a native Deno ChildProcessSpawner implementation with shared cross-platform process conformance.
  • Tests
    • Expanded child-process spawning tests for environment/working directory, shell vs non-shell behavior, stdout/stderr streaming, pipelines, signals/timeouts, error handling, and supervision/cleanup.
    • Added option-gated additional file-descriptor coverage and deadlock regression checks.
  • Test Fixtures
    • Added bash and config fixtures to support permission, early-exit, and long-running process-tree scenarios.
  • Documentation
    • Documented Deno-specific ChildProcess limitations and divergences.
  • Refactor
    • Switched Node child-process tests to reuse the shared conformance suite.

@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jul 28, 2026
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 20ba4df

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 29 packages
Name Type
@effect/platform-deno Patch
effect Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/openapi-generator Patch

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

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tim-smart, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 819948e6-fea7-4126-ad7a-a60093a5ec57

📥 Commits

Reviewing files that changed from the base of the PR and between 6f7e159 and 20ba4df.

📒 Files selected for processing (1)
  • packages/platform-deno/test/DenoChildProcessSpawner.test.ts
📝 Walkthrough

Walkthrough

A native Deno ChildProcessSpawner is added with shared conformance coverage for spawning, pipelines, streams, file descriptors, errors, and process supervision. Node tests now use the shared suite, while Deno-specific limitations and release metadata are documented.

Changes

Child process spawning

Layer / File(s) Summary
Deno spawner implementation
packages/platform-deno/src/DenoChildProcessSpawner.ts, packages/platform-deno/src/index.ts
Implements Deno command execution, stream handling, lifecycle control, pipeline flattening, unsupported-option errors, and public exports.
Shared suite and process I/O coverage
packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
Adds reusable coverage for command execution, environments, shells, pipelines, streams, stdin, additional file descriptors, errors, and output deadlock regressions.
Process supervision and lifecycle fixtures
packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts, packages/effect/test/unstable/process/fixtures/bash/*
Tests process-group cleanup, unref behavior, exit-code resolution, forced termination, and pipeline lifecycle handling with long-running process trees.
Node and Deno test adapters
packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts, packages/platform-deno/test/DenoChildProcessSpawner.test.ts
Replaces Node’s local test hierarchy with the shared suite and adds Deno filesystem wiring plus Deno-specific option, pipeline, and shell tests.
Fixtures and Deno documentation
packages/effect/test/unstable/process/fixtures/config/*, packages/platform-platform-deno/README.md, .changeset/eff-141-deno-child-process.md
Adds command fixtures and documents Deno divergences and the package patch release.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DenoTest as DenoChildProcessSpawner.test
  participant Suite as ChildProcessSpawnerTest.suite
  participant Spawner as DenoChildProcessSpawner
  participant Deno as Deno.Command
  participant Streams as Process streams
  DenoTest->>Suite: configure shared suite with Deno layer
  Suite->>Spawner: spawn command or pipeline
  Spawner->>Deno: execute command with stdio options
  Deno-->>Streams: provide stdin, stdout, and stderr
  Streams-->>Suite: return output and exit status
  Suite->>Suite: assert process behavior
Loading

Possibly related PRs

  • Effect-TS/effect#6412: Covers related ChildProcessSpawner additionalFds behavior and Deno’s explicit rejection of that option.
  • Effect-TS/effect#6652: Overlaps with shared tests for process environment handling and extendEnv behavior.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@effect-slopcop effect-slopcop Bot added 4.0 enhancement New feature or request labels Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

File Name Current Size Previous Size Difference
basic.ts 6.63 KB 6.63 KB 0.00 KB (0.00%)
batching.ts 9.37 KB 9.37 KB 0.00 KB (0.00%)
brand.ts 6.26 KB 6.26 KB 0.00 KB (0.00%)
cache.ts 10.12 KB 10.12 KB 0.00 KB (0.00%)
config.ts 19.19 KB 19.19 KB 0.00 KB (0.00%)
differ.ts 18.42 KB 18.42 KB 0.00 KB (0.00%)
http-client.ts 20.88 KB 20.88 KB 0.00 KB (0.00%)
logger.ts 10.28 KB 10.28 KB 0.00 KB (0.00%)
metric.ts 8.55 KB 8.55 KB 0.00 KB (0.00%)
optic.ts 7.41 KB 7.41 KB 0.00 KB (0.00%)
pubsub.ts 14.20 KB 14.20 KB 0.00 KB (0.00%)
queue.ts 11.09 KB 11.09 KB 0.00 KB (0.00%)
schedule.ts 10.27 KB 10.27 KB 0.00 KB (0.00%)
schema-class.ts 18.16 KB 18.16 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 28.02 KB 28.02 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 24.34 KB 24.34 KB 0.00 KB (0.00%)
schema-string-transformation.ts 12.69 KB 12.69 KB 0.00 KB (0.00%)
schema-string.ts 10.35 KB 10.35 KB 0.00 KB (0.00%)
schema-template-literal.ts 14.49 KB 14.49 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 20.93 KB 20.93 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 23.38 KB 23.38 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 18.32 KB 18.32 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.00 KB 18.00 KB 0.00 KB (0.00%)
schema-toFormatter.ts 17.88 KB 17.88 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 21.52 KB 21.52 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 18.56 KB 18.56 KB 0.00 KB (0.00%)
schema.ts 17.43 KB 17.43 KB 0.00 KB (0.00%)
stm.ts 12.05 KB 12.05 KB 0.00 KB (0.00%)
stream.ts 9.37 KB 9.37 KB 0.00 KB (0.00%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (4)
packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts (3)

219-221: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Await exitCode after draining stdout, not before.

Every other test reads stdout first. Here exitCode is awaited while stdout is still buffered; it only passes because ls output is smaller than the pipe buffer. Swap the two lines to match the rest of the suite and stay deadlock-free if a fixture dir grows.

♻️ Proposed reorder
               const handle = yield* ChildProcess.make`ls ${args} ${dir}`
-              const exitCode = yield* handle.exitCode
               const output = yield* decodeByteStream(handle.stdout)
+              const exitCode = yield* handle.exitCode
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts` around
lines 219 - 221, In the ChildProcess test flow, reorder the awaits so
decodeByteStream(handle.stdout) drains stdout before awaiting handle.exitCode.
Keep the existing ChildProcess.make invocation and assertions unchanged.

262-306: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Serial draining of multiple child pipes. Both sites read one stream to completion before starting the next, which only works while every stream's payload fits in the OS pipe buffer — the exact hazard the comments on lines 311 and 326 call out and solve with concurrent reads.

  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts#L262-L306: wrap the stdout/stderr reads in Effect.all([...], { concurrency: "unbounded" }).
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts#L865-L883: wrap the stdout, stderr, and getOutputFd(3) reads in a single Effect.all([...], { concurrency: "unbounded" }).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts` around
lines 262 - 306, The stdout and stderr pipes are drained serially, risking
blockage when one exceeds the OS pipe buffer. In ChildProcessSpawnerTest.ts
lines 262-306, update both affected tests to read stdout and stderr through a
single Effect.all with unbounded concurrency; in ChildProcessSpawnerTest.ts
lines 865-883, similarly combine stdout, stderr, and getOutputFd(3) reads into
one concurrent Effect.all.

887-902: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

killMatchingProcesses runs an unescaped pkill -f — worth hardening.

pattern is interpolated straight into a single-quoted shell string, and pattern[0] is undefined for an empty string (yielding [undefined]). Callers currently pass safe literals, so this is not exploitable, but a stray pattern here issues a broad pkill -f against the whole machine. Consider asserting the pattern is a non-empty [A-Za-z0-9_-]+ before building the command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts` around
lines 887 - 902, Validate pattern in killMatchingProcesses before constructing
the pkill command, requiring a non-empty value containing only ASCII letters,
digits, underscores, or hyphens; reject invalid input rather than interpolating
it, and retain the existing escaped-pattern behavior for valid values.
packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh (1)

7-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Give the descendant sleeps a unique marker.

These subshells run a bare sleep 30, which is what forces the test to count sleep 30 globally via ps aux. Passing a distinctive $0 makes the process identifiable and the assertions precise.

🔧 Proposed fix
     (
       grandchild_pid=$BASHPID
       echo "Grandchild of child $i started with PID $grandchild_pid"
-      # Keep running for 30 seconds
-      sleep 30
+      # Keep running for 30 seconds, tagged so tests can identify it
+      sh -c 'sleep 30' parent-exits-early-descendant
     ) &
 
-    # Keep the child running
-    sleep 30
+    # Keep the child running, tagged so tests can identify it
+    sh -c 'sleep 30' parent-exits-early-descendant
   ) &
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh`
around lines 7 - 24, Update the descendant sleep commands in the
parent-exits-early fixture to pass a distinctive process marker via $0, using
different identifiable markers for the child and grandchild sleeps. Preserve the
30-second duration while enabling process assertions to target only these
descendants instead of counting all global “sleep 30” processes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts`:
- Around line 712-730: Update the assertion in the “should throw permission
denied as a typed error” test to validate only the runtime-independent
PlatformError fields: _tag, module, and method. Remove the exact syscall and
pathOrDescriptor expectations so the shared test remains compatible across
adapters, leaving runtime-specific payload shape checks to adapter-specific
tests.
- Around line 340-401: Make the .all interleaving tests deterministic by
removing exact cross-stream ordering assumptions from “should read interspersed
stdout and stderr via .all” and “should handle many lines of interspersed output
via .all”. Assert that all expected stdout and stderr lines are present while
preserving each stream’s internal order, or use a sufficiently reliable
synchronization strategy instead of 10 ms sleeps.
- Around line 988-999: Replace the global `ps aux` search for the generic `sleep
30` pattern in the affected cleanup assertions with a unique marker identifying
descendants launched by `parent-exits-early.sh`. Update that script’s `sleep`
subshell invocations to receive the marker via `$0`, following the existing
pattern near the pipeline unref test, and have both assertions count only that
marker while preserving their expected zero-process checks.
- Around line 1003-1021: Ensure the process created in the “should not kill an
unrefed process when scope closes” test is always killed by attaching cleanup
through Effect.ensuring or Effect.addFinalizer, rather than leaving handle.kill
as the final statement after assert.isTrue. Hoist handle or use a scoped
finalizer so cleanup runs when assertions fail, and apply the same
finalizer-based cleanup to the pipeline test around its cleanup logic.
- Around line 142-153: Update the shell-expansion test around ChildProcess.make
to use a test-controlled environment variable with a known non-empty value
instead of HOME, passing that variable into the spawned shell command and
asserting the expanded output excludes the literal variable expression.
- Around line 19-33: Update decodeByteStream to handle ChildProcess.Encoding
values other than text encodings before constructing TextDecoder: either
restrict its accepted encoding type to supported text encodings or add explicit
decoding branches for base64, base64url, binary, and hex. Ensure unsupported
values no longer reach new TextDecoder(encoding) and preserve the existing
text-decoding behavior.

In `@packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh`:
- Around line 18-26: Update both unused-count loops in spawn-children.sh to
discard the loop variable by using _ instead of j, preserving the existing
60-iteration sleep behavior and eliminating SC2034.

---

Nitpick comments:
In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts`:
- Around line 219-221: In the ChildProcess test flow, reorder the awaits so
decodeByteStream(handle.stdout) drains stdout before awaiting handle.exitCode.
Keep the existing ChildProcess.make invocation and assertions unchanged.
- Around line 262-306: The stdout and stderr pipes are drained serially, risking
blockage when one exceeds the OS pipe buffer. In ChildProcessSpawnerTest.ts
lines 262-306, update both affected tests to read stdout and stderr through a
single Effect.all with unbounded concurrency; in ChildProcessSpawnerTest.ts
lines 865-883, similarly combine stdout, stderr, and getOutputFd(3) reads into
one concurrent Effect.all.
- Around line 887-902: Validate pattern in killMatchingProcesses before
constructing the pkill command, requiring a non-empty value containing only
ASCII letters, digits, underscores, or hyphens; reject invalid input rather than
interpolating it, and retain the existing escaped-pattern behavior for valid
values.

In `@packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh`:
- Around line 7-24: Update the descendant sleep commands in the
parent-exits-early fixture to pass a distinctive process marker via $0, using
different identifiable markers for the child and grandchild sleeps. Preserve the
30-second duration while enabling process assertions to target only these
descendants instead of counting all global “sleep 30” processes.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 88ca1ff7-98e0-401f-ba70-9f66f6badc32

📥 Commits

Reviewing files that changed from the base of the PR and between 45e7810 and add42d9.

📒 Files selected for processing (9)
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
  • packages/effect/test/unstable/process/fixtures/bash/no-permissions.sh
  • packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh
  • packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh
  • packages/effect/test/unstable/process/fixtures/config/SHOUTING
  • packages/effect/test/unstable/process/fixtures/config/integer
  • packages/effect/test/unstable/process/fixtures/config/nested/config
  • packages/effect/test/unstable/process/fixtures/config/secret
  • packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts

Comment thread packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
Comment thread packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
Comment thread packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
Comment thread packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
Comment thread packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
Comment thread packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
@github-project-automation github-project-automation Bot moved this from Discussion Ongoing to Waiting on Author in PR Backlog Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 7

🧹 Nitpick comments (4)
packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts (3)

219-221: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Await exitCode after draining stdout, not before.

Every other test reads stdout first. Here exitCode is awaited while stdout is still buffered; it only passes because ls output is smaller than the pipe buffer. Swap the two lines to match the rest of the suite and stay deadlock-free if a fixture dir grows.

♻️ Proposed reorder
               const handle = yield* ChildProcess.make`ls ${args} ${dir}`
-              const exitCode = yield* handle.exitCode
               const output = yield* decodeByteStream(handle.stdout)
+              const exitCode = yield* handle.exitCode
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts` around
lines 219 - 221, In the ChildProcess test flow, reorder the awaits so
decodeByteStream(handle.stdout) drains stdout before awaiting handle.exitCode.
Keep the existing ChildProcess.make invocation and assertions unchanged.

262-306: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Serial draining of multiple child pipes. Both sites read one stream to completion before starting the next, which only works while every stream's payload fits in the OS pipe buffer — the exact hazard the comments on lines 311 and 326 call out and solve with concurrent reads.

  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts#L262-L306: wrap the stdout/stderr reads in Effect.all([...], { concurrency: "unbounded" }).
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts#L865-L883: wrap the stdout, stderr, and getOutputFd(3) reads in a single Effect.all([...], { concurrency: "unbounded" }).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts` around
lines 262 - 306, The stdout and stderr pipes are drained serially, risking
blockage when one exceeds the OS pipe buffer. In ChildProcessSpawnerTest.ts
lines 262-306, update both affected tests to read stdout and stderr through a
single Effect.all with unbounded concurrency; in ChildProcessSpawnerTest.ts
lines 865-883, similarly combine stdout, stderr, and getOutputFd(3) reads into
one concurrent Effect.all.

887-902: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

killMatchingProcesses runs an unescaped pkill -f — worth hardening.

pattern is interpolated straight into a single-quoted shell string, and pattern[0] is undefined for an empty string (yielding [undefined]). Callers currently pass safe literals, so this is not exploitable, but a stray pattern here issues a broad pkill -f against the whole machine. Consider asserting the pattern is a non-empty [A-Za-z0-9_-]+ before building the command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts` around
lines 887 - 902, Validate pattern in killMatchingProcesses before constructing
the pkill command, requiring a non-empty value containing only ASCII letters,
digits, underscores, or hyphens; reject invalid input rather than interpolating
it, and retain the existing escaped-pattern behavior for valid values.
packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh (1)

7-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Give the descendant sleeps a unique marker.

These subshells run a bare sleep 30, which is what forces the test to count sleep 30 globally via ps aux. Passing a distinctive $0 makes the process identifiable and the assertions precise.

🔧 Proposed fix
     (
       grandchild_pid=$BASHPID
       echo "Grandchild of child $i started with PID $grandchild_pid"
-      # Keep running for 30 seconds
-      sleep 30
+      # Keep running for 30 seconds, tagged so tests can identify it
+      sh -c 'sleep 30' parent-exits-early-descendant
     ) &
 
-    # Keep the child running
-    sleep 30
+    # Keep the child running, tagged so tests can identify it
+    sh -c 'sleep 30' parent-exits-early-descendant
   ) &
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh`
around lines 7 - 24, Update the descendant sleep commands in the
parent-exits-early fixture to pass a distinctive process marker via $0, using
different identifiable markers for the child and grandchild sleeps. Preserve the
30-second duration while enabling process assertions to target only these
descendants instead of counting all global “sleep 30” processes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts`:
- Around line 712-730: Update the assertion in the “should throw permission
denied as a typed error” test to validate only the runtime-independent
PlatformError fields: _tag, module, and method. Remove the exact syscall and
pathOrDescriptor expectations so the shared test remains compatible across
adapters, leaving runtime-specific payload shape checks to adapter-specific
tests.
- Around line 340-401: Make the .all interleaving tests deterministic by
removing exact cross-stream ordering assumptions from “should read interspersed
stdout and stderr via .all” and “should handle many lines of interspersed output
via .all”. Assert that all expected stdout and stderr lines are present while
preserving each stream’s internal order, or use a sufficiently reliable
synchronization strategy instead of 10 ms sleeps.
- Around line 988-999: Replace the global `ps aux` search for the generic `sleep
30` pattern in the affected cleanup assertions with a unique marker identifying
descendants launched by `parent-exits-early.sh`. Update that script’s `sleep`
subshell invocations to receive the marker via `$0`, following the existing
pattern near the pipeline unref test, and have both assertions count only that
marker while preserving their expected zero-process checks.
- Around line 1003-1021: Ensure the process created in the “should not kill an
unrefed process when scope closes” test is always killed by attaching cleanup
through Effect.ensuring or Effect.addFinalizer, rather than leaving handle.kill
as the final statement after assert.isTrue. Hoist handle or use a scoped
finalizer so cleanup runs when assertions fail, and apply the same
finalizer-based cleanup to the pipeline test around its cleanup logic.
- Around line 142-153: Update the shell-expansion test around ChildProcess.make
to use a test-controlled environment variable with a known non-empty value
instead of HOME, passing that variable into the spawned shell command and
asserting the expanded output excludes the literal variable expression.
- Around line 19-33: Update decodeByteStream to handle ChildProcess.Encoding
values other than text encodings before constructing TextDecoder: either
restrict its accepted encoding type to supported text encodings or add explicit
decoding branches for base64, base64url, binary, and hex. Ensure unsupported
values no longer reach new TextDecoder(encoding) and preserve the existing
text-decoding behavior.

In `@packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh`:
- Around line 18-26: Update both unused-count loops in spawn-children.sh to
discard the loop variable by using _ instead of j, preserving the existing
60-iteration sleep behavior and eliminating SC2034.

---

Nitpick comments:
In `@packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts`:
- Around line 219-221: In the ChildProcess test flow, reorder the awaits so
decodeByteStream(handle.stdout) drains stdout before awaiting handle.exitCode.
Keep the existing ChildProcess.make invocation and assertions unchanged.
- Around line 262-306: The stdout and stderr pipes are drained serially, risking
blockage when one exceeds the OS pipe buffer. In ChildProcessSpawnerTest.ts
lines 262-306, update both affected tests to read stdout and stderr through a
single Effect.all with unbounded concurrency; in ChildProcessSpawnerTest.ts
lines 865-883, similarly combine stdout, stderr, and getOutputFd(3) reads into
one concurrent Effect.all.
- Around line 887-902: Validate pattern in killMatchingProcesses before
constructing the pkill command, requiring a non-empty value containing only
ASCII letters, digits, underscores, or hyphens; reject invalid input rather than
interpolating it, and retain the existing escaped-pattern behavior for valid
values.

In `@packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh`:
- Around line 7-24: Update the descendant sleep commands in the
parent-exits-early fixture to pass a distinctive process marker via $0, using
different identifiable markers for the child and grandchild sleeps. Preserve the
30-second duration while enabling process assertions to target only these
descendants instead of counting all global “sleep 30” processes.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 88ca1ff7-98e0-401f-ba70-9f66f6badc32

📥 Commits

Reviewing files that changed from the base of the PR and between 45e7810 and add42d9.

📒 Files selected for processing (9)
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
  • packages/effect/test/unstable/process/fixtures/bash/no-permissions.sh
  • packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh
  • packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh
  • packages/effect/test/unstable/process/fixtures/config/SHOUTING
  • packages/effect/test/unstable/process/fixtures/config/integer
  • packages/effect/test/unstable/process/fixtures/config/nested/config
  • packages/effect/test/unstable/process/fixtures/config/secret
  • packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts
🛑 Comments failed to post (1)
packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh (1)

18-26: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Silence SC2034 by discarding the unused loop variable.

j is never read in either loop. Use _ (or the seq-free for ((j=0; j<60; j++)) form) so ShellCheck stops warning.

🔧 Proposed fix
       # Keep running for 60 seconds
-      for j in {1..60}; do
+      for _ in {1..60}; do
         sleep 1
       done
     ) &
 
     # Keep the child running
-    for j in {1..60}; do
+    for _ in {1..60}; do
       sleep 1
     done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      for _ in {1..60}; do
        sleep 1
      done
    ) &

    # Keep the child running
    for _ in {1..60}; do
      sleep 1
    done
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 24-24: j appears unused. Verify use (or export if used externally).

(SC2034)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh` around
lines 18 - 26, Update both unused-count loops in spawn-children.sh to discard
the loop variable by using _ instead of j, preserving the existing 60-iteration
sleep behavior and eliminating SC2034.

Source: Linters/SAST tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
packages/platform-deno/src/DenoChildProcessSpawner.ts (4)

351-353: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant Stream.unwrap(Effect.succeed(...)).

getSourceStream already returns a Stream; the wrapper is a no-op.

♻️ Proposed simplification
-          const sourceStream = Stream.unwrap(
-            Effect.succeed(getSourceStream(handles[handles.length - 1], options.from))
-          )
+          const sourceStream = getSourceStream(handles[handles.length - 1], options.from)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/platform-deno/src/DenoChildProcessSpawner.ts` around lines 351 -
353, In the source stream setup, replace the redundant
Stream.unwrap(Effect.succeed(...)) wrapper with the Stream returned directly by
getSourceStream, preserving the same handles and options.from arguments.

48-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider always attaching cause.

For NotFound/PermissionDenied/TimedOut the originating Deno.errors.* instance (and its stack) is discarded, leaving only the derived tag. Keeping cause in all branches costs nothing and preserves diagnostics.

♻️ Proposed tweak
   return PlatformError.systemError({
     _tag: tag,
     module: "ChildProcess",
     method,
     pathOrDescriptor: commandString(command),
     syscall: `${method} ${commandString(command).trim()}`,
-    ...(tag === "Unknown" ? { cause } : undefined)
+    cause
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/platform-deno/src/DenoChildProcessSpawner.ts` around lines 48 - 55,
Update the PlatformError.systemError construction in DenoChildProcessSpawner so
the original cause is always attached, regardless of the error tag. Remove the
conditional restriction tied to the "Unknown" tag while preserving the existing
_tag, module, method, pathOrDescriptor, and syscall fields.

247-249: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

detached: false is rejected even though it is satisfiable.

Deno's behavior is non-detached, so detached: false can be honored as a no-op. Rejecting on mere presence forces portable callers to strip an explicitly-default-valued option. Rejecting only detached: true would widen cross-platform compatibility (the corresponding assertion in packages/platform-deno/test/DenoChildProcessSpawner.test.ts would need updating to use detached: true).

♻️ Proposed change
-        if (Predicate.isNotUndefined(cmd.options.detached)) {
+        if (cmd.options.detached === true) {
           return yield* Effect.fail(unsupported("detached"))
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/platform-deno/src/DenoChildProcessSpawner.ts` around lines 247 -
249, Update the detached-option handling in DenoChildProcessSpawner so it
rejects only when cmd.options.detached is true, while treating detached: false
as a no-op. Update the corresponding test assertion in
DenoChildProcessSpawner.test.ts to verify rejection for detached: true.

407-439: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract flattenCommand into packages/effect/src/unstable/process/ChildProcess.ts. It only depends on ChildProcess.Command, and packages/platform-node-shared/src/NodeChildProcessSpawner.ts already carries the same helper, so centralizing it would remove duplication and keep both spawners on the shared API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/platform-deno/src/DenoChildProcessSpawner.ts` around lines 407 -
439, Move the FlattenedPipeline interface and flattenCommand helper from
DenoChildProcessSpawner into the shared ChildProcess module in
packages/effect/src/unstable/process/ChildProcess.ts. Export them there, then
update both DenoChildProcessSpawner and NodeChildProcessSpawner to import and
reuse the shared symbols, removing their local duplicate implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/platform-deno/README.md`:
- Line 13: Update the README statement about killing a ChildProcess to say that
descendant processes remain running, rather than saying they are not reaped;
preserve the existing explanation about Deno lacking portable process-group
isolation.

In `@packages/platform-deno/src/DenoChildProcessSpawner.ts`:
- Around line 366-385: Update the pipeline handle construction around makeHandle
so kill terminates every handle in handles, invoking them in reverse order so
the tail is stopped first. Keep the existing tail-handle forwarding for pid,
exitCode, isRunning, and stream fields, and preserve the current unref behavior.
- Around line 232-235: Update the PipedCommand handling around the default
`fromOption` branch to reject additional file-descriptor sources with the same
`unsupported("additionalFds")` behavior used by the existing `to` validation. Do
not resolve valid fd names through `handle.getOutputFd`, and ensure unparsable
fd-style `from` values no longer silently fall back to stdout.

In `@packages/platform-deno/test/DenoChildProcessSpawner.test.ts`:
- Around line 33-38: Update makeTempDirectoryScoped so the incoming
options.directory value is translated to Deno.makeTempDir’s dir option before
invoking it, while preserving the remaining options and existing platformError
handling.

---

Nitpick comments:
In `@packages/platform-deno/src/DenoChildProcessSpawner.ts`:
- Around line 351-353: In the source stream setup, replace the redundant
Stream.unwrap(Effect.succeed(...)) wrapper with the Stream returned directly by
getSourceStream, preserving the same handles and options.from arguments.
- Around line 48-55: Update the PlatformError.systemError construction in
DenoChildProcessSpawner so the original cause is always attached, regardless of
the error tag. Remove the conditional restriction tied to the "Unknown" tag
while preserving the existing _tag, module, method, pathOrDescriptor, and
syscall fields.
- Around line 247-249: Update the detached-option handling in
DenoChildProcessSpawner so it rejects only when cmd.options.detached is true,
while treating detached: false as a no-op. Update the corresponding test
assertion in DenoChildProcessSpawner.test.ts to verify rejection for detached:
true.
- Around line 407-439: Move the FlattenedPipeline interface and flattenCommand
helper from DenoChildProcessSpawner into the shared ChildProcess module in
packages/effect/src/unstable/process/ChildProcess.ts. Export them there, then
update both DenoChildProcessSpawner and NodeChildProcessSpawner to import and
reuse the shared symbols, removing their local duplicate implementations.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 515e8047-8fa2-4741-9ecb-d5620dae8986

📥 Commits

Reviewing files that changed from the base of the PR and between add42d9 and c093387.

📒 Files selected for processing (6)
  • .changeset/eff-141-deno-child-process.md
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
  • packages/platform-deno/README.md
  • packages/platform-deno/src/DenoChildProcessSpawner.ts
  • packages/platform-deno/src/index.ts
  • packages/platform-deno/test/DenoChildProcessSpawner.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts

Comment thread packages/platform-deno/README.md Outdated
Comment thread packages/platform-deno/src/DenoChildProcessSpawner.ts Outdated
Comment thread packages/platform-deno/src/DenoChildProcessSpawner.ts
Comment thread packages/platform-deno/test/DenoChildProcessSpawner.test.ts
Comment thread packages/platform-deno/src/DenoChildProcessSpawner.ts Outdated

const resolveStdinOption = (options: ChildProcess.CommandOptions): ChildProcess.StdinConfig => {
const defaultConfig: ChildProcess.StdinConfig = { stream: "pipe", encoding: "utf-8", endOnDone: true }
if (Predicate.isUndefined(options.stdin)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Instead of Predicate.isUndefined just use normal undefined checks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replaced the Predicate.isUndefined checks with direct undefined checks throughout the adapter.

Comment thread packages/platform-deno/src/DenoChildProcessSpawner.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/platform-deno/test/DenoChildProcessSpawner.test.ts`:
- Around line 110-130: Update the “kills every process in a pipeline” test
around ChildProcess.pipeTo so the downstream sleep command independently updates
a second heartbeat file. Capture both heartbeat sizes after handle.kill({
killSignal: "SIGKILL" }), wait with TestClock.withLive, then assert each final
size remains unchanged, proving both the root and pipeline child processes
stopped.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e9bd472-3c95-472e-a1f5-d1911d77bab8

📥 Commits

Reviewing files that changed from the base of the PR and between c093387 and 6f7e159.

📒 Files selected for processing (6)
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts
  • packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh
  • packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh
  • packages/platform-deno/README.md
  • packages/platform-deno/src/DenoChildProcessSpawner.ts
  • packages/platform-deno/test/DenoChildProcessSpawner.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/platform-deno/README.md
  • packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh
  • packages/platform-deno/src/DenoChildProcessSpawner.ts
  • packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts

Comment thread packages/platform-deno/test/DenoChildProcessSpawner.test.ts Outdated
@tim-smart tim-smart changed the title Extract shared ChildProcessSpawner conformance tests Add DenoChildProcessSpawner Jul 28, 2026
@tim-smart
tim-smart enabled auto-merge (squash) July 28, 2026 09:33
@tim-smart
tim-smart merged commit 504343b into main Jul 28, 2026
15 checks passed
@tim-smart
tim-smart deleted the agent/codex-engineer/eff-141-deno-child-process-spawner branch July 28, 2026 09:34
@github-project-automation github-project-automation Bot moved this from Waiting on Author to Done in PR Backlog Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants