Skip to content

fix(js-sdk): unpin useDefineForClassFields — make caller-directory resolution emit-invariant#1539

Open
mishushakov wants to merge 1 commit into
mainfrom
fix-usedefineforclassfields
Open

fix(js-sdk): unpin useDefineForClassFields — make caller-directory resolution emit-invariant#1539
mishushakov wants to merge 1 commit into
mainfrom
fix-usedefineforclassfields

Conversation

@mishushakov

Copy link
Copy Markdown
Member

Follow-up to #1536, which pinned useDefineForClassFields: false in the js-sdk tsconfig because raising target to es2022 flips the default to true, and that broke the template builder. This PR fixes the root cause and removes the pin, so the SDK now compiles with the standard es2022 [[Define]] class-field semantics.

Root cause

TemplateBase resolved its default fileContextPath in a class field initializer:

private fileContextPath: PathLike =
  runtime === 'browser' ? '.' : (getCallerDirectory(STACK_TRACE_DEPTH) ?? '.')

With native class fields (define semantics), V8 evaluates field initializers in an extra <instance_members_initializer> stack frame:

at getCallerDirectory (utils.ts)
at <instance_members_initializer> (index.ts)   ← extra frame under define semantics
at new TemplateBase (index.ts)
at Template (index.ts)
at user code                                    ← fixed-depth walk lands one frame short

getCallerDirectory walks the stack at a fixed depth, so it landed on the SDK's own src/template directory instead of the caller's — .copy('folder/*', …) then globbed against the wrong base dir (Error: No files found in .../src/template/...), and the resulting client-side failure mis-attributed build-step stack traces (the two stacktrace.test.ts failures were cascades of this one bug).

Fix

Move the default resolution into the constructor body, where the stack shape is identical under both emits:

constructor(options?: TemplateOptions) {
  this.fileContextPath =
    options?.fileContextPath ??
    (runtime === 'browser' ? '.' : (getCallerDirectory(STACK_TRACE_DEPTH) ?? '.'))

The call is now emit-invariant (same STACK_TRACE_DEPTH), so the tsconfig pin is removed. The method-level getCallerFrame call sites were never affected — method bodies don't change shape with class-field semantics.

Only the js-sdk is touched: the Python SDKs resolve the caller via inspect and don't have this failure mode, and the CLI bundle doesn't include TemplateBase.

Usage example

Fixes relative-path resolution for SDK consumers whose toolchain emits native class fields (e.g. esbuild/vitest with target: es2022+):

// user-project/scripts/template.ts
const template = Template()
  .fromBaseImage()
  .copy('assets/*', '/app/assets') // now resolves against user-project/scripts/,
                                   // not the SDK's own directory

Verification

  • tests/template/stacktrace.test.ts — 30/30 pass with the flag defaulted (true), and still 30/30 when explicitly set back to false (emit-invariance)
  • tests/template/build.test.ts — 4/4 pass against the real backend (real .copy glob + build)
  • Smoke-tested built dist/index.mjs and dist/index.js from an external directory: fileContextPath resolves to the importing script's directory in both
  • Unit project A/B: identical results with and without this change (remaining failures are pre-existing E2B_API_KEY-gated live tests)
  • pnpm run typecheck, lint, format

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 75c63ed

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

This PR includes changesets to release 1 package
Name Type
e2b 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

@cursor

cursor Bot commented Jul 9, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches how relative copy paths and file hashing resolve for all Node template builds, but the change is localized and targets a known stack-trace bug.

Overview
With target: es2022, default [[Define]] class-field emit added an extra stack frame when getCallerDirectory ran in a field initializer, so the template builder’s default fileContextPath pointed at the SDK instead of the caller and relative .copy() paths failed.

Default resolution now runs in the TemplateBase constructor (still honoring fileContextPath in options), which keeps stack depth stable across emits. The useDefineForClassFields: false workaround was removed from packages/js-sdk/tsconfig.json. A patch changeset documents the fix.

Reviewed by Cursor Bugbot for commit 75c63ed. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from 1377cc2. Download artifacts from this workflow run.

JS SDK (e2b@2.32.1-fix-usedefineforclassfields.0):

npm install ./e2b-2.32.1-fix-usedefineforclassfields.0.tgz

CLI (@e2b/cli@2.13.2-fix-usedefineforclassfields.0):

npm install ./e2b-cli-2.13.2-fix-usedefineforclassfields.0.tgz

Python SDK (e2b==2.31.0+fix.usedefineforclassfields):

pip install ./e2b-2.31.0+fix.usedefineforclassfields-py3-none-any.whl

Resolve the template builder's default fileContextPath in the constructor
body instead of a class field initializer. With [[Define]] class-field
semantics (the default at target es2022+), field initializers run in an
extra <instance_members_initializer> stack frame, shifting the fixed-depth
stack walk in getCallerDirectory by one — .copy() sources resolved against
the SDK's own directory instead of the caller's. The constructor-body call
is emit-invariant, so the useDefineForClassFields: false pin in the js-sdk
tsconfig is no longer needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mishushakov mishushakov force-pushed the fix-usedefineforclassfields branch from 9cd65a4 to 75c63ed Compare July 9, 2026 18:44
@mishushakov mishushakov enabled auto-merge (squash) July 9, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant