Skip to content

parser: esbuild ESM bundle with CJS helper shims misclassified as CommonJS → CJS-wrapped → parse fails (ImportExportInScript / shebang ExpectedIdent) #5498

Closed

Description

@proggeramlug

Summary

An esbuild ESM bundle that also contains esbuild's CommonJS helper machinery (the __commonJS/__require-shim it emits for CJS deps it bundled) is misclassified as CommonJS and run through Perry's CJS-to-ESM wrap. The file is unambiguously an ES module — it has top-level import { createRequire } from "module" and import.meta.url — so wrapping it as a CJS script makes its genuine top-level import illegal and the parse fails before any real compilation begins.

Real-world target: the OpenAI Codex CLI, last Node/TS release before its Rust rewrite.

Repro

# 3.9MB esbuild ESM bundle (dist/cli.js); type:module
npm pack @openai/codex@0.1.2505291658
tar xzfO openai-codex-0.1.2505291658.tgz package/dist/cli.js > codex-cli.js

perry compile codex-cli.js -o /tmp/codex

(In our corpus this is reached as a compilePackages target, which is what triggers the wrap path — see note below.)

Observed

Error: Failed to parse .../codex-cli.js: Parse error: Error { error: (4918..4919, ExpectedIdent) }
note: this file is inside a `compilePackages` target and was rewritten by Perry's
      CJS-to-ESM wrap before parsing.

Two layered symptoms, one root cause:

  1. Shebang not stripped before the wrap. The source is spliced into the CJS wrapper template with its leading #!/usr/bin/env node intact, so the # lands mid-file → ExpectedIdent at offset 4918.
  2. The deeper bug. Strip the shebang and the next error is ImportExportInScript (offset 5808) on the bundle's real top-level import { createRequire as NDe } from "module". The wrap put genuine ESM into a CJS script context.

The file is ESM by any measure: top-level import ... from "module" and 5× import.meta.url.

Expected

A file with top-level import/export is an ES module and must not be CJS-wrapped, regardless of esbuild's __commonJS/createRequire helpers also appearing in it. The classifier should treat the presence of top-level import/export as decisive ESM. (Shebang-stripping on the wrap path is a secondary fix on the same code, worth doing for any genuine CJS file that carries a shebang.)

Why it's a misclassification, not a Codex quirk

The @anthropic-ai/claude-code cli.js bundle (also a large esbuild ESM bundle, same compilePackages corpus setup) is correctly recognized as ESM, never wrapped, and parses straight through to compile-time semantics (it stops on a Function(...) AOT refusal at line 100). The only difference is that the Codex bundle inlined some CJS deps, so esbuild emitted its CJS helper shims — and those shims are what trip the classifier into the CJS branch.

Environment

  • perry 0.5.1167
  • @openai/codex@0.1.2505291658 (dist/cli.js, ~3.9MB, type: module)

Activity

  1. added a commit that references this issue on Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions