Skip to content

Map socket writer exceptions and rejections to SocketError - #6987

Merged
tim-smart merged 3 commits into
mainfrom
audit/repro-unstable-socket-socket-write-defect
Aug 4, 2026
Merged

Map socket writer exceptions and rejections to SocketError#6987
tim-smart merged 3 commits into
mainfrom
audit/repro-unstable-socket-socket-write-defect

Conversation

@fubhy

@fubhy fubhy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Transform-stream write rejection and synchronous WebSocket send errors become defects rather than typed SocketError failures, bypassing the recovery promised by Socket.writer.

Important

This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.

Socket writer failures escape as defects

Module: Socket
Audit ID: unstable-http-socket-writer-defects
Severity / confidence: medium / high

What happens

Transform-stream write rejection and synchronous WebSocket send errors become defects rather than typed SocketError failures, bypassing the recovery promised by Socket.writer.

Why it happens

The WebSocket writer wraps send in Effect.sync, making synchronous exceptions defects, and the transform-stream writer uses Effect.promise, making rejected WritableStream.write promises defects.

Expected behavior

Socket.writer returns effects whose declared failure channel is SocketError.

Relevant implementation

These links and excerpts are pinned to audit base c9b56ab507f224426ee8388dc450da447ec4715f.

View problematic code at packages/effect/src/unstable/socket/Socket.ts:738-746
    const write = (chunk: Uint8Array | string | CloseEvent) =>
      latch.whenOpen(Effect.sync(() => {
        const ws = currentWS!
        if (isCloseEvent(chunk)) {
          ws.close(chunk.code, chunk.reason)
        } else {
          ws.send(chunk as string | Uint8Array<ArrayBuffer>)
        }
      }))

View exact lines on GitHub

View problematic code at packages/effect/src/unstable/socket/Socket.ts:898-910
    const write = (chunk: Uint8Array | string | CloseEvent) =>
      latch.whenOpen(Effect.suspend(() => {
        const { fiberSet, stream } = currentStream!
        if (isCloseEvent(chunk)) {
          return Deferred.fail(
            fiberSet.deferred,
            new SocketError({
              reason: new SocketCloseError({ code: chunk.code, closeReason: chunk.reason })
            })
          )
        }
        return Effect.promise(() => getWriter(stream).write(typeof chunk === "string" ? encoder.encode(chunk) : chunk))
      }))

View exact lines on GitHub

Reproduction

pnpm test --run packages/platform-node/test/NodeSocket.test.ts

Observed failure: Failed as intended because the transform-stream write rejection produced Die(Error: write failed), not Fail(SocketError).

Implementation handoff

The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.

  1. Start with the pinned implementation excerpts and the Why it happens analysis above.
  2. Change the implementation so it satisfies the stated Expected behavior; do not weaken or remove the reproduction assertions.
  3. Run the focused reproduction command(s) and confirm the observed failures become passing tests:
pnpm test --run packages/platform-node/test/NodeSocket.test.ts
  1. Run the affected package's existing tests, then the repository lint and type checks before requesting review.

Audit provenance

  • Audit base: c9b56ab507f224426ee8388dc450da447ec4715f
  • Reproduction base: c9b56ab507f224426ee8388dc450da447ec4715f
  • Findings: unstable-http-socket-writer-defects
  • Initial patch: focused reproduction tests; implementation fix pending

Closes EFF-424

@fubhy fubhy added the audit Findings originating from the Effect runtime correctness audit label Aug 4, 2026
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 90c942a

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

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno 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/doctest 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

@effect-slopcop effect-slopcop Bot added 4.0 bug Something isn't working labels Aug 4, 2026

@pullfrog pullfrog 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.

ℹ️ No critical issues — the added regression test correctly reproduces the defect, but the specification could be tightened and expanded before the implementation fix lands.

Reviewed changes

Reviewed the single-file diff against fab7668. It adds one focused test in packages/platform-node/test/NodeSocket.test.ts that expects a WritableStream write rejection from Socket.fromTransformStream to be surfaced as a SocketError failure rather than a defect.

  • The new test currently fails as intended: the output is Die(Error: write failed) instead of Fail(SocketError).
  • The test only covers the fromTransformStream path; the PR title and description also call out the WebSocket send path, which is not yet covered.
  • The assertion validates Socket.SocketError.is(reason.error) but does not enforce that the reason is a SocketWriteError, which is the convention used by the platform socket adapters (NodeSocket, DenoSocket).

ℹ️ WebSocket writer path is not covered by the new regression

The PR title and description highlight both fromTransformStream writable rejections and synchronous WebSocket#send exceptions. The current diff only adds a test for the transform-stream side. Before the implementation fix is added, consider adding a matching test for the WebSocket writer so both paths are pinned. This can be done by providing a WebSocketConstructor that returns a subclass of the mock WebSocket whose send method throws synchronously.

ℹ️ Nitpicks

  • The transform-stream test name is accurate, but the PR title suggests broader scope; either expand the test or adjust the title when the implementation lands.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix all ➔Fix 👍s ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

Comment thread packages/platform-node/test/NodeSocket.test.ts
Comment thread packages/platform-node/test/NodeSocket.test.ts Outdated
}
}))
latch.whenOpen(
Effect.try({

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.

Let's use Effect.suspend and normal try / catch instead

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review (fab7668): the implementation fix has landed and the prior feedback has been addressed.

  • packages/effect/src/unstable/socket/Socket.ts now maps both writer failure paths to SocketError with a SocketWriteError reason: synchronous WebSocket#send exceptions via a try/catch in fromWebSocket, and writable-stream write rejections via Effect.tryPromise in fromTransformStream.
  • packages/platform-node/test/NodeSocket.test.ts gained a regression test for the WebSocket send-throws path and both tests now assert that the error reason is specifically SocketWriteError, matching the convention used by NodeSocket and DenoSocket.
  • .changeset/soft-sockets-write.md adds a concise patch changeset for effect.

Focused tests pass and pnpm lint-fix is clean. Prior Pullfrog review threads have been resolved.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@tim-smart
tim-smart enabled auto-merge (squash) August 4, 2026 23:26
@tim-smart
tim-smart merged commit 7f12d4b into main Aug 4, 2026
18 of 19 checks passed
@tim-smart
tim-smart deleted the audit/repro-unstable-socket-socket-write-defect branch August 4, 2026 23:48
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 7.06 KB 7.06 KB 0.00 KB (0.00%)
batching.ts 9.86 KB 9.86 KB 0.00 KB (0.00%)
brand.ts 6.34 KB 6.34 KB 0.00 KB (0.00%)
cache.ts 10.71 KB 10.71 KB 0.00 KB (0.00%)
config.ts 20.60 KB 20.60 KB 0.00 KB (0.00%)
differ.ts 20.20 KB 20.20 KB 0.00 KB (0.00%)
http-client.ts 21.54 KB 21.54 KB -0.00 KB (-0.01%)
logger.ts 10.84 KB 10.84 KB 0.00 KB (0.00%)
metric.ts 8.98 KB 8.98 KB 0.00 KB (0.00%)
optic.ts 7.18 KB 7.18 KB 0.00 KB (0.00%)
pubsub.ts 14.99 KB 14.99 KB 0.00 KB (0.00%)
queue.ts 11.66 KB 11.66 KB 0.00 KB (0.00%)
schedule.ts 10.83 KB 10.83 KB 0.00 KB (0.00%)
schema-class.ts 19.14 KB 19.14 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 28.96 KB 28.96 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.29 KB 25.29 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.38 KB 13.38 KB 0.00 KB (0.00%)
schema-string.ts 10.94 KB 10.94 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.17 KB 15.17 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 21.94 KB 21.94 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.34 KB 24.34 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.18 KB 19.18 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.01 KB 19.01 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.87 KB 18.87 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.60 KB 22.60 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.52 KB 19.52 KB 0.00 KB (0.00%)
schema.ts 18.41 KB 18.41 KB 0.00 KB (0.00%)
stm.ts 12.63 KB 12.63 KB 0.00 KB (0.00%)
stream.ts 9.80 KB 9.80 KB 0.00 KB (0.00%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 audit Findings originating from the Effect runtime correctness audit bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants