What version of Effect is running?
Latest
What steps can reproduce the bug?
Bug: Effect incompatible with Temporal workflow sandbox due to Error.stackTraceLimit assignments
Summary
Effect cannot be used in Temporal TypeScript workflow code because Effect directly assigns to Error.stackTraceLimit, which is read-only in Temporal's sandboxed workflow environment. This causes an immediate runtime error when Effect is imported.
Environment
- Effect version: 3.19.14 (also tested with 3.14.7)
- Node.js version: 22.x
- Temporal SDK version: 1.14.1
- OS: macOS (also reproducible on Linux)
Description
Temporal runs workflow code in a sandboxed V8 isolate to ensure determinism. In this sandbox, certain global properties are made read-only, including Error.stackTraceLimit. Effect modifies this property in approximately 30 places throughout the codebase for stack trace management purposes.
When Effect is imported in a Temporal workflow, the following error occurs:
Cannot assign to read only property 'stackTraceLimit' of function 'class SandboxedError extends Error { ... }'
Note on Previous Temporal Fix
PR #4196 (commit 5c67bdc) addressed a different Temporal compatibility issue by avoiding putting symbols in globalThis. That fix resolved symbol pollution but did not address the stackTraceLimit issue, which is a separate problem.
Reproduction
Minimal reproduction (simulated sandbox)
// test-effect-sandbox.cjs
// Simulate Temporal sandbox - make Error.stackTraceLimit read-only
Object.defineProperty(Error, 'stackTraceLimit', {
value: Error.stackTraceLimit,
writable: false,
configurable: false
});
console.log('Error.stackTraceLimit is now read-only');
try {
const Effect = require('effect');
console.log('SUCCESS: Effect loaded without errors');
} catch (error) {
console.log('FAILURE:', error.message);
}
Output:
Error.stackTraceLimit is now read-only
FAILURE: Cannot assign to read only property 'stackTraceLimit' of function 'class Error { [native code] }'
Full reproduction with Temporal
// workflows.ts
import { Effect, pipe } from 'effect';
export async function myWorkflow(input: string): Promise<string> {
const program = pipe(
Effect.succeed(input),
Effect.map((s) => s.toUpperCase())
);
return Effect.runPromise(program);
}
// worker.ts
import { Worker } from '@temporalio/worker';
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
taskQueue: 'test',
});
await worker.run();
Result: Worker fails to start with stackTraceLimit error during workflow bundling/execution.
Affected Code Locations
The following files contain direct stackTraceLimit assignments:
| File |
Occurrences |
Pattern |
src/Effect.ts |
12 |
Error.stackTraceLimit = 2 |
src/internal/runtime.ts |
6 |
Error.stackTraceLimit = 0 |
src/internal/context.ts |
4 |
Error.stackTraceLimit = 2 |
src/internal/tracer.ts |
2 |
Error.stackTraceLimit = 3 |
src/Micro.ts |
2 |
globalThis.Error.stackTraceLimit = 2 |
src/LayerMap.ts |
2 |
Err.stackTraceLimit = 2 |
| Other internal files |
~2 |
Various |
Common pattern in the codebase:
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 2; // <-- This line throws in Temporal sandbox
const error = new Error();
Error.stackTraceLimit = limit;
Expected Behavior
Effect should handle environments where Error.stackTraceLimit is read-only without throwing an error. The stack trace optimization is a performance enhancement and should fail gracefully.
Proposed Solution
Wrap stackTraceLimit assignments in try/catch blocks:
Before:
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 2;
const error = new Error();
Error.stackTraceLimit = limit;
After:
const limit = Error.stackTraceLimit;
try { Error.stackTraceLimit = 2; } catch {}
const error = new Error();
try { Error.stackTraceLimit = limit; } catch {}
This maintains the optimization in standard environments while gracefully degrading in restricted environments like Temporal's sandbox.
Alternative: Check writability first
const stackTraceLimitWritable = Object.getOwnPropertyDescriptor(Error, 'stackTraceLimit')?.writable !== false;
// Later in code:
if (stackTraceLimitWritable) {
Error.stackTraceLimit = 2;
}
This approach avoids the try/catch overhead but requires a one-time check at module initialization.
Current Workaround
Users can work around this issue using a Temporal webpack plugin that transforms the bundled code:
class StackTraceLimitTransformPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('StackTraceLimitTransformPlugin', (compilation, callback) => {
for (const [filename, asset] of Object.entries(compilation.assets)) {
if (filename.endsWith('.js')) {
let source = asset.source();
source = source
.replace(
/globalThis\.Error\.stackTraceLimit\s*=\s*([^;,\n\r)]+)/g,
'(function(){try{globalThis.Error.stackTraceLimit=$1}catch(e){}})() '
)
.replace(
/(?<![.\w])Error\.stackTraceLimit\s*=\s*([^;,\n\r)]+)/g,
'(function(){try{Error.stackTraceLimit=$1}catch(e){}})() '
)
.replace(
/Err\.stackTraceLimit\s*=\s*([^;,\n\r)]+)/g,
'(function(){try{Err.stackTraceLimit=$1}catch(e){}})() '
);
compilation.assets[filename] = {
source: () => source,
size: () => source.length,
};
}
}
callback();
});
}
}
// In worker configuration:
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
taskQueue: 'my-queue',
bundlerOptions: {
webpackConfigHook: (config) => {
config.plugins = config.plugins || [];
config.plugins.push(new StackTraceLimitTransformPlugin());
return config;
},
},
});
This workaround successfully transforms all 30 stackTraceLimit assignments in the Effect bundle, allowing Effect to work in Temporal workflows.
Impact
This issue prevents using Effect in Temporal workflows, which limits adoption for teams that want to combine:
- Effect's powerful composition and error handling
- Temporal's durable execution and workflow orchestration
Temporal is a popular workflow engine with growing adoption, and Effect compatibility would benefit both communities.
Related Issues
Additional Context
A full POC demonstrating the issue and workaround is available attached (sorry it's a zip)
The POC includes:
- Simulated sandbox test confirming the issue
- Temporal worker configurations for testing
- Webpack plugin workaround
- Bundle analysis showing transformation results
Archive.zip
What is the expected behavior?
No response
What do you see instead?
No response
Additional information
No response
What version of Effect is running?
Latest
What steps can reproduce the bug?
Bug: Effect incompatible with Temporal workflow sandbox due to Error.stackTraceLimit assignments
Summary
Effect cannot be used in Temporal TypeScript workflow code because Effect directly assigns to
Error.stackTraceLimit, which is read-only in Temporal's sandboxed workflow environment. This causes an immediate runtime error when Effect is imported.Environment
Description
Temporal runs workflow code in a sandboxed V8 isolate to ensure determinism. In this sandbox, certain global properties are made read-only, including
Error.stackTraceLimit. Effect modifies this property in approximately 30 places throughout the codebase for stack trace management purposes.When Effect is imported in a Temporal workflow, the following error occurs:
Note on Previous Temporal Fix
PR #4196 (commit 5c67bdc) addressed a different Temporal compatibility issue by avoiding putting symbols in
globalThis. That fix resolved symbol pollution but did not address thestackTraceLimitissue, which is a separate problem.Reproduction
Minimal reproduction (simulated sandbox)
Output:
Full reproduction with Temporal
Result: Worker fails to start with
stackTraceLimiterror during workflow bundling/execution.Affected Code Locations
The following files contain direct
stackTraceLimitassignments:src/Effect.tsError.stackTraceLimit = 2src/internal/runtime.tsError.stackTraceLimit = 0src/internal/context.tsError.stackTraceLimit = 2src/internal/tracer.tsError.stackTraceLimit = 3src/Micro.tsglobalThis.Error.stackTraceLimit = 2src/LayerMap.tsErr.stackTraceLimit = 2Common pattern in the codebase:
Expected Behavior
Effect should handle environments where
Error.stackTraceLimitis read-only without throwing an error. The stack trace optimization is a performance enhancement and should fail gracefully.Proposed Solution
Wrap
stackTraceLimitassignments in try/catch blocks:Before:
After:
This maintains the optimization in standard environments while gracefully degrading in restricted environments like Temporal's sandbox.
Alternative: Check writability first
This approach avoids the try/catch overhead but requires a one-time check at module initialization.
Current Workaround
Users can work around this issue using a Temporal webpack plugin that transforms the bundled code:
This workaround successfully transforms all 30
stackTraceLimitassignments in the Effect bundle, allowing Effect to work in Temporal workflows.Impact
This issue prevents using Effect in Temporal workflows, which limits adoption for teams that want to combine:
Temporal is a popular workflow engine with growing adoption, and Effect compatibility would benefit both communities.
Related Issues
Additional Context
A full POC demonstrating the issue and workaround is available attached (sorry it's a zip)
The POC includes:
Archive.zip
What is the expected behavior?
No response
What do you see instead?
No response
Additional information
No response