Skip to content

[APPS-2792] Harden local Node execution: signal reporting, shutdown cleanup, edge-case coverage - #471

Draft
tyffical wants to merge 1 commit into
tiffany.trinh/apps-2792-local-node-execution-pocfrom
tiffany.trinh/apps-2792-harden-local-execution
Draft

[APPS-2792] Harden local Node execution: signal reporting, shutdown cleanup, edge-case coverage#471
tyffical wants to merge 1 commit into
tiffany.trinh/apps-2792-local-node-execution-pocfrom
tiffany.trinh/apps-2792-harden-local-execution

Conversation

@tyffical

@tyffical tyffical commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Motivation

Changes

What changed File
Report the OS signal (e.g. SIGSEGV, SIGKILL) when a child is killed rather than exiting normally, instead of an opaque "exited with code null" packages/plugins/apps/src/vite/local-execution.ts
Track live children in a Set; export killAllLocalExecutionChildren() for the dev server to call on its own shutdown so in-flight executions don't leave orphaned Node processes packages/plugins/apps/src/vite/local-execution.ts
Add getMostRecentlyForkedChildForTest() — test-only accessor to exercise real signal-based kills packages/plugins/apps/src/vite/local-execution.ts
Add regression coverage for behavior that already worked but had no test: non-serializable return values (circular references) reject cleanly instead of hanging, concurrent $.Actions calls within one execution resolve to their own correct results packages/plugins/apps/src/vite/local-execution.integration.test.ts
Add tests for the new signal-reporting and shutdown-cleanup behavior, using a real forked child (not mocked) packages/plugins/apps/src/vite/local-execution.integration.test.ts

QA Instructions

yarn install
BUILD_PLUGINS_ENV=test FORCE_COLOR=true JEST_CONFIG_TRANSPILE_ONLY=true VITE_CJS_IGNORE_WARNING=true NODE_OPTIONS="--experimental-vm-modules" yarn workspace @dd/tests exec jest --config jest.config.ts packages/plugins/apps/src/vite/local-execution.integration.test.ts
# Expected: 6 tests passing ✅ VERIFIED
BUILD_PLUGINS_ENV=test FORCE_COLOR=true JEST_CONFIG_TRANSPILE_ONLY=true VITE_CJS_IGNORE_WARNING=true NODE_OPTIONS="--experimental-vm-modules" yarn workspace @dd/tests exec jest --config jest.config.ts packages/plugins/apps
# Expected: 24 test suites, 291 tests, all passing ✅ VERIFIED
cd packages/plugins/apps && npx tsc -b tsconfig.client.json --emitDeclarationOnly && npx tsc --noEmit
# Expected: no output, clean exit ✅ VERIFIED
yarn eslint packages/plugins/apps/src/vite/local-execution.ts packages/plugins/apps/src/vite/local-execution.integration.test.ts
# Expected: no output, clean exit ✅ VERIFIED

Manual QA — not yet possible, same as #461

Same reasoning as #461, which this PR is stacked on: createDevServerMiddleware doesn't call anything in local-execution.ts, so there's no npm run dev request path reaching this code yet — nothing to click through locally or on staging. Did do real local + staging manual QA on the separately-scoped #460 (ssr:true + noExternal fix), since that one is on a live path today. Manual QA for this file becomes possible once Milestone 2 wires it into the dev server.

Blast Radius

  • This file (local-execution.ts) is not wired into createDevServerMiddleware yet — Milestone 2, a separate stacked PR — so this change has zero effect on any currently-shipping behavior.
  • Risk: low. Purely additive hardening to code that isn't live yet.
  • executeActionRemotely remains a stub — real wiring is blocked on the single-action execution endpoint decision (design doc's Open Dependency section), not this PR's scope.

Out of Scope / Follow-ups

Item Status Next step
Process pooling (reuse instead of fork-per-call) Deferred Explicitly deferred per the design doc's Timeline section — the measured fork-per-call latency (p50 36ms) already beats today's cloud round-trip by orders of magnitude; pooling is a Should-have optimization once usage data justifies the added complexity, not a P0 blocker
handleExecuteAction integration into real dev-server.ts Done DataDog/build-plugins#473, stacked on this PR

Documentation

tyffical added a commit that referenced this pull request Jul 28, 2026
- npm run dev's /__dd/executeAction now runs backend functions via
  executeScriptLocally (a real forked child process) instead of the cloud
  preview-async round trip, per the design doc's Rollout section: no
  customer-facing toggle, local execution is the new unconditional default.
- Preserves today's cloud round trip as a new, additive
  /__dd/executeActionViaCloud endpoint rather than deleting it -- no
  `npm run dev:verify` script exists yet to replace it (that requires
  create-apps scaffolding template changes, outside this package), and the
  existing dev-server.test.ts suite has extensive coverage of the cloud path
  that would otherwise be silently orphaned.
- /__dd/executeAction no longer requires Datadog credentials to be
  configured, unlike /__dd/executeActionViaCloud -- local execution's
  $.Actions calls are stubbed today (see local-execution.ts), so there's
  nothing for it to authenticate yet.
- Wires killAllLocalExecutionChildren() (from #471) into the dev server's
  own shutdown via server.httpServer's 'close' event, so killing the dev
  server mid-execution doesn't leave an orphaned child process.
- Registers local-exec-child.js in the package's buildPlugin.toBuild config
  (same mechanism apps-runtime.ts already uses) so it's actually copied
  into the published dist/src output next to the compiled bundle.
  local-execution.ts resolves it at runtime via a __dirname-relative path,
  which only works if the file exists on disk next to the bundle -- found
  by running a real npm-linked build against a scaffolded app, since the
  existing jest tests resolve local-execution.ts's source directly and
  never exercise the published package layout.

$.Actions calls remain stubbed until the single-action execution endpoint
resolves (design doc's Open Dependency section) -- this is explicitly not
customer-ready yet, same as the rest of this PR stack.

Milestone 2 of APPS-2792's local Node execution kickoff plan.
… coverage

- Report the OS signal (SIGSEGV/SIGKILL/etc.) when a child is killed rather
  than exiting normally, instead of an opaque "exited with code null" --
  distinguishes an OOM kill or native-module crash from a plain crash.
- Track live children in a Set; export killAllLocalExecutionChildren() for
  the dev server to call on its own shutdown so executions in flight don't
  leave orphaned Node processes behind.
- Add regression coverage for behavior that already worked but had no test:
  non-serializable return values (circular references) produce a clean
  rejection instead of hanging, and concurrent $.Actions calls within a
  single execution resolve to their own correct results.

Milestone 1 of APPS-2792's local Node execution kickoff plan.
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2792-harden-local-execution branch from 4ac0179 to 780b7c7 Compare July 29, 2026 18:12
tyffical added a commit that referenced this pull request Jul 29, 2026
- npm run dev's /__dd/executeAction now runs backend functions via
  executeScriptLocally (a real forked child process) instead of the cloud
  preview-async round trip, per the design doc's Rollout section: no
  customer-facing toggle, local execution is the new unconditional default.
- Preserves today's cloud round trip as a new, additive
  /__dd/executeActionViaCloud endpoint rather than deleting it -- no
  `npm run dev:verify` script exists yet to replace it (that requires
  create-apps scaffolding template changes, outside this package), and the
  existing dev-server.test.ts suite has extensive coverage of the cloud path
  that would otherwise be silently orphaned.
- /__dd/executeAction no longer requires Datadog credentials to be
  configured, unlike /__dd/executeActionViaCloud -- local execution's
  $.Actions calls are stubbed today (see local-execution.ts), so there's
  nothing for it to authenticate yet.
- Wires killAllLocalExecutionChildren() (from #471) into the dev server's
  own shutdown via server.httpServer's 'close' event, so killing the dev
  server mid-execution doesn't leave an orphaned child process.
- Registers local-exec-child.js in the package's buildPlugin.toBuild config
  (same mechanism apps-runtime.ts already uses) so it's actually copied
  into the published dist/src output next to the compiled bundle.
  local-execution.ts resolves it at runtime via a __dirname-relative path,
  which only works if the file exists on disk next to the bundle -- found
  by running a real npm-linked build against a scaffolded app, since the
  existing jest tests resolve local-execution.ts's source directly and
  never exercise the published package layout.

$.Actions calls remain stubbed until the single-action execution endpoint
resolves (design doc's Open Dependency section) -- this is explicitly not
customer-ready yet, same as the rest of this PR stack.

Milestone 2 of APPS-2792's local Node execution kickoff plan.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant