Skip to content

fix: support frozen intrinsics when adjusting Error.stackTraceLimit#1

Closed
bweis wants to merge 1 commit into
mainfrom
fix/frozen-intrinsics-stack-trace-limit
Closed

fix: support frozen intrinsics when adjusting Error.stackTraceLimit#1
bweis wants to merge 1 commit into
mainfrom
fix/frozen-intrinsics-stack-trace-limit

Conversation

@bweis

@bweis bweis commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Ports Effect-TS/effect#6279 to effect-smol.

Upstream PR (same branch, base Effect-TS/effect-smol:main): Effect-TS#2444

Type

  • Bug Fix

Description

In hardened/deterministic JavaScript environments (SES "frozen intrinsics", Temporal-style sandboxes), Error is frozen and Error.stackTraceLimit is read-only. Effect mutates stackTraceLimit in several internal spots to capture short or empty stack traces cheaply, using the pattern:

const limit = Error.stackTraceLimit
Error.stackTraceLimit = 2
const err = new Error()
Error.stackTraceLimit = limit

In these environments the assignments throw, breaking Effect entirely.

This PR adds packages/effect/src/internal/stackTraceLimit.ts, which checks writability once at module load (mirroring Node's internal guard) and exposes getStackTraceLimit() / setStackTraceLimit() helpers where set is a silent no-op when the property can't be modified. All raw mutations are replaced with these guarded calls, with new Error() kept inline at each call site so the captured stack trace still points at the real caller rather than the helper.

Call sites updated: Context.ts, Layer.ts, LayerMap.ts, internal/effect.ts (cause pretty-errors, Effect.fn), internal/tracer.ts, unstable/httpapi/HttpApiMiddleware.ts, unstable/rpc/RpcMiddleware.ts.

Differences from the upstream PR

The effect-smol layout differs from Effect-TS/effect:

  • Internals are consolidated (internal/effect.ts, public Context.ts/Layer.ts/LayerMap.ts) rather than split across cause/core-effect/runtime/layer/context; there is no Micro.ts.
  • The new helper reuses the existing ErrorWithStackTraceLimit interface and .ts import extensions.
  • unsafeSecureJsonParse (unstable/ai/Tool.ts) imports the internal helper directly instead of duplicating the guard, because ai lives in the same package here (unlike @effect/ai upstream).

Behavior in normal (writable) environments is unchanged. A changeset and a dedicated test (packages/effect/test/StackTraceLimit.test.ts, covering both the writable and frozen paths) are included; typecheck and lint pass and the affected suites (Cause, Tracer, Context, LayerMap, Effect) are green.

Note

The original change was stood up by @arlyon. This description and PR were aided via Claude Code.

In hardened/deterministic JavaScript environments (SES "frozen intrinsics",
Temporal-style sandboxes), `Error` is frozen and `Error.stackTraceLimit` is
read-only. Effect mutates `stackTraceLimit` in several internal spots to
capture short/empty stack traces cheaply; under frozen intrinsics those
assignments throw, breaking Effect entirely.

Add an internal `internal/stackTraceLimit.ts` helper (mirroring Node's own
guard) that detects writability once at module load and degrades `set` to a
silent no-op when the property can't be modified. Replace the raw mutations
across Context.ts, Layer.ts, LayerMap.ts, internal/effect.ts, internal/tracer.ts,
HttpApiMiddleware.ts and RpcMiddleware.ts with guarded get/set calls, keeping
`new Error()` inline at each call site so captured stack traces still point at
the real caller.

`unsafeSecureJsonParse` in unstable/ai/Tool.ts imports the same internal helper
directly (it lives in the same package here, unlike `@effect/ai` upstream).

Ports Effect-TS/effect#6279 to effect-smol. Original change stood up by @arlyon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant