revert(execute): drop runtime swap in #430 — keep prod on bun#431
Merged
Conversation
PR #430 switched docker/app/start.sh from 'exec bun src/server.ts' to 'exec node --import tsx src/server.ts' so isolated-vm could load. End-to-end smoke in the live image confirmed the runtime swap itself worked, but the resulting prod image crashloops on boot: SyntaxError: The requested module '@lobu/core' does not provide an export named 'createBuiltinSecretRef' at packages/owletto-backend/src/lobu/stores/postgres-secret-store.ts:13 The export exists in @lobu/core/dist/secret-refs.js. Node's cjs-module-lexer detects it when the file is the sole entry, but fails to detect it when reached via the full server.ts boot chain. Bun has its own CJS↔ESM interop and didn't hit this. Fixing properly requires shipping @lobu/core as dual ESM+CJS instead of CJS-only behind an 'import' condition. Reverting unblocks the partial deploy. Old pods are still serving traffic (CrashLoopBackOff prevented rollover). The execute tool returns to its pre-#430 state — same RuntimeUnavailable behavior that's been in prod since #427 hardened the load failure. No user-facing regression beyond what we already had this morning. Tracked: convert @lobu/core (and other workspace packages with the same shape) to dual ESM+CJS so the runtime swap can land cleanly.
Contributor
|
Triage decision: needs-human Reasons:
Next: Manual review and merge by assigned human reviewer |
This was referenced Apr 27, 2026
buremba
added a commit
that referenced
this pull request
Apr 28, 2026
) Execute MCP tool has been broken in prod since launch: the backend runs under Bun (JSC, partial V8 ABI shim) but isolated-vm is a V8 native addon that fails to dlopen under Bun. Returns RuntimeUnavailable to every caller. #430 tried 'exec node --import tsx src/server.ts' to switch the runtime. The runtime swap mechanism worked end-to-end (verified isolated-vm + runScript() in the live image), but the actual server boot crashed because Node's cjs-module-lexer couldn't detect named exports of @lobu/core's CJS dist barrel when reached via the full server.ts import chain. Reverted in #431. Right fix per second-opinion review: bundle the backend at build time. esbuild resolves all workspace imports inline (using the 'bun' condition to pick up TS source rather than CJS dist), so Node never has to bind named imports against cross-package CJS barrels — the failure mode that broke #430 simply doesn't exist for the bundle. Native addons and packages with require-in-the-middle hooks (Sentry, OpenTelemetry, pino) stay external so their runtime hooks keep working. Implementation: - packages/owletto-backend/scripts/build-server-bundle.mjs: esbuild config that bundles workspace + relative imports, externalizes every bare specifier. ~2.4 MB self-contained ESM, ~100 ms build. - packages/owletto-backend/package.json: 'build:server' npm script. - docker/app/Dockerfile: switch 'bun install' to 'bun install --linker=hoisted' so node_modules is flat (Node's resolver doesn't understand bun's .bun/<pkg>@<ver>/ isolated layout). Add a 'bun run build:server' step in the builder stage. - docker/app/start.sh: 'exec node /app/.../dist/server.bundle.mjs' instead of 'exec bun src/server.ts'. Verified end-to-end against a locally built docker image: basic: {success: true, returnValue: 3} chaining: {success: true, returnValue: {slug: 'atlas', ok: true}} The CI sandbox-runtime test added in #430 stays as the regression guard - it'll keep failing until the runtime can actually load isolated-vm. Tradeoffs: - Adds ~5 s to the docker build (esbuild bundle). - Hoisted layout is npm-style flat: increases build-stage install time slightly but matches what Node expects natively. - Native addons and Sentry/OpenTelemetry/pino externalized; if any of those moves are wrong they'll surface at boot, not silently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
PR #430 switched
docker/app/start.shfromexec bun src/server.tstoexec node --import tsx src/server.tsso isolated-vm could load. The runtime swap mechanism itself works (verified end-to-end in the live image: runScript() returns the expected result). But the resulting prod image crashloops on boot:The export exists in
@lobu/core/dist/secret-refs.js. Node's cjs-module-lexer detects it when the file is the sole entry — but fails to detect it when reached via the fullserver.tsboot chain. Bun's CJS↔ESM interop didn't hit this.Real fix: ship
@lobu/core(and other workspace packages with the same shape) as dual ESM+CJS instead of CJS-only behind animportcondition.Status
56765bdd9c-*) are still Running. RollingUpdate's maxUnavailable check blocked rollover when the new pod crashlooped, so traffic is unaffected.RuntimeUnavailable(the fix(execute): harden sandbox runtime #427 hardening). Same broken-but-not-crashing state we had this morning.Out of scope
Test plan