require.main === module is true inside every compiled CommonJS module, not just the process entry point.
crates/perry/src/commands/compile/cjs_wrap/wrap.rs:1138 — Perry's CJS preamble unconditionally emits
require.main = module; for every compiled CommonJS module. So the standard entry-point check is trivially
true everywhere.
Minimal repro (no dependencies)
A throwaway CJS package containing console.log(require.main === module), required from another file:
Why this matters more than it looks
if (require.main === module) { … } is the CommonJS idiom for "am I being run directly, or merely imported?".
Packages use it to decide whether to run CLI behaviour — parse process.argv, print usage, call process.exit().
Under Perry every such package takes the CLI branch when it is merely imported as a library.
Observed concretely: with the native dotenv binding removed and real dotenv@18.0.1 compiled from source, a plain
import dotenv from "dotenv" prints dotenv's bundled CLI usage banner and the process exits 1 instead of 0
— purely from this. The library functions themselves work correctly.
This was masked, and that is the interesting part
The defect is general and pre-existing, not introduced by any recent change. It was invisible because the native
dotenv binding intercepted the import, so the real npm source was never compiled. Removing the binding is what
first exercises the path.
That is worth noting for the binding-removal campaign generally (#10678 and the removal PRs): every binding we
delete stops masking whatever compiler defects the real package's code exercises. This is the second finding of
that shape today — the first being agent-base's TypeScript namespace + export = handling, surfaced by
compiling real axios (#10662 → PR #10673). Expect more, and treat them as latent debt being uncovered rather
than as regressions caused by the removals.
Fix direction
require.main should be set to the entry module only, so it compares equal in the process entry point and
unequal everywhere else. The preamble needs to distinguish the entry module from a required dependency — that
distinction already exists elsewhere in the compile path, since Perry knows which module is the entry.
Found while rebasing PR #10691 (remove dotenv native binding); not fixed there, since it is a compiler-wide CJS
defect rather than anything dotenv-specific.
require.main === moduleistrueinside every compiled CommonJS module, not just the process entry point.crates/perry/src/commands/compile/cjs_wrap/wrap.rs:1138— Perry's CJS preamble unconditionally emitsrequire.main = module;for every compiled CommonJS module. So the standard entry-point check is triviallytrue everywhere.
Minimal repro (no dependencies)
A throwaway CJS package containing
console.log(require.main === module), required from another file:falsetrueWhy this matters more than it looks
if (require.main === module) { … }is the CommonJS idiom for "am I being run directly, or merely imported?".Packages use it to decide whether to run CLI behaviour — parse
process.argv, print usage, callprocess.exit().Under Perry every such package takes the CLI branch when it is merely imported as a library.
Observed concretely: with the native dotenv binding removed and real
dotenv@18.0.1compiled from source, a plainimport dotenv from "dotenv"prints dotenv's bundled CLI usage banner and the process exits 1 instead of 0— purely from this. The library functions themselves work correctly.
This was masked, and that is the interesting part
The defect is general and pre-existing, not introduced by any recent change. It was invisible because the native
dotenv binding intercepted the import, so the real npm source was never compiled. Removing the binding is what
first exercises the path.
That is worth noting for the binding-removal campaign generally (#10678 and the removal PRs): every binding we
delete stops masking whatever compiler defects the real package's code exercises. This is the second finding of
that shape today — the first being
agent-base's TypeScriptnamespace+export =handling, surfaced bycompiling real axios (#10662 → PR #10673). Expect more, and treat them as latent debt being uncovered rather
than as regressions caused by the removals.
Fix direction
require.mainshould be set to the entry module only, so it compares equal in the process entry point andunequal everywhere else. The preamble needs to distinguish the entry module from a required dependency — that
distinction already exists elsewhere in the compile path, since Perry knows which module is the entry.
Found while rebasing PR #10691 (remove dotenv native binding); not fixed there, since it is a compiler-wide CJS
defect rather than anything dotenv-specific.