← Back to PRs

#16019: fix(plugins): add postinstall patch for ESM-only package exports

by dashed open 2026-02-14 05:28 View on GitHub →
scripts size: L
## Summary Fixes plugin loading failures caused by ESM-only dependencies that lack CJS-compatible export conditions. - **Root cause**: jiti (the TS/ESM loader used for plugins) converts `import` to CJS `require()` internally. Three dependencies (`@buape/carbon`, `osc-progress`, `@mariozechner/pi-coding-agent`) ship export maps with only an `"import"` condition — no `"default"` or `"require"` fallback — causing `ERR_PACKAGE_PATH_NOT_EXPORTED` at runtime. This silently breaks **all** plugin loading for any plugin importing from `openclaw/plugin-sdk`. - **Fix**: A postinstall script (`scripts/patch-esm-exports.cjs`) that walks `node_modules` and adds the missing `"default"` export condition to any package whose exports have `"import"` but neither `"default"` nor `"require"`. The patch is idempotent, has zero runtime cost, and becomes a no-op if upstream packages add CJS support. - **Scope**: Affects all deployment modes (npm, Docker, source) — not just custom Docker builds. Most users never notice because plugin failures are non-fatal and the web channel (compiled into the main bundle) works regardless. ### Related issues - #12854 (same jiti ESM/CJS boundary class, reverse direction) - #7312 (ESM/CJS barriers blocking OTel instrumentation) - #15686 (plugins fail to load, gateway continues) - #7668 (request for e2e tests to catch module errors) ## Test plan - [x] **22 unit tests** (`src/scripts/patch-esm-exports.test.ts`) — `patchExports` logic, `patchDir` directory walking, edge cases (malformed JSON, string shorthand, idempotency, depth limits, skip dirs), affected package simulations - [x] **10 e2e tests** (`src/scripts/patch-esm-exports.e2e.test.ts`): - Reproduces `ERR_PACKAGE_PATH_NOT_EXPORTED` with ESM-only fixtures (proves bug exists) - Confirms `patchDir` resolves the failure (proves fix works) - Validates all 3 affected packages resolve via CJS `require.resolve` in real `node_modules` - Verifies real jiti can resolve `@buape/carbon` through patched exports - [x] `pnpm check` clean (format + typecheck + lint) <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> Adds a postinstall script (`scripts/patch-esm-exports.cjs`) that patches ESM-only dependencies (`@buape/carbon`, `osc-progress`, `@mariozechner/pi-coding-agent`) by adding a `"default"` export condition to their `package.json` exports maps. This resolves `ERR_PACKAGE_PATH_NOT_EXPORTED` errors when jiti (the runtime TS/ESM loader) converts `import` to CJS `require()` internally. - The patch script is idempotent, never exits non-zero, and becomes a no-op once upstream packages add CJS support - Includes 22 unit tests and 10 e2e tests that reproduce the actual error and verify the fix - The `patchExports` function handles one-level-deep condition maps (subpath → condition → value), which is sufficient for all three affected packages - E2e tests verify real `require.resolve` and jiti resolution after patching <h3>Confidence Score: 5/5</h3> - This PR is safe to merge — it fixes a real runtime error with an idempotent, zero-runtime-cost postinstall patch backed by thorough tests. - The patch script is well-structured with proper error handling, never exits non-zero, and is idempotent. The fix is minimal and targeted — it only adds a missing "default" condition to packages that have "import" but no CJS fallback. 32 tests (22 unit + 10 e2e) comprehensively verify both the patching logic and the real-world resolution of affected packages. The code handles edge cases like malformed JSON, symlinks, depth limits, and skipped directories. No functional issues were found. - No files require special attention. <sub>Last reviewed commit: f6e58e3</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs