Skip to content

revert(execute): drop runtime swap in #430 — keep prod on bun#431

Merged
buremba merged 1 commit into
mainfrom
revert/execute-runtime-node
Apr 27, 2026
Merged

revert(execute): drop runtime swap in #430 — keep prod on bun#431
buremba merged 1 commit into
mainfrom
revert/execute-runtime-node

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented Apr 27, 2026

Why

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. 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:

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'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 an import condition.

Status

Out of scope

Test plan

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.
@github-actions github-actions Bot added the triage:needs-human Triage agent escalated for human review label Apr 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Triage decision: needs-human

Reasons:

  • Modified file docker/app/start.sh is under docker/ infra path
  • Infra changes require manual review due to blast radius concerns
  • Per triage config, any path under docker/ escalates to needs-human

Next: Manual review and merge by assigned human reviewer

@buremba buremba merged commit f6115d8 into main Apr 27, 2026
14 checks passed
@buremba buremba deleted the revert/execute-runtime-node branch April 27, 2026 23:02
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.
@buremba buremba restored the revert/execute-runtime-node branch May 12, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage:needs-human Triage agent escalated for human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant