[APPS-2792] Wire local Node execution into the real dev server - #473
Draft
tyffical wants to merge 2 commits into
Draft
Conversation
This was referenced 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.
@datadog/apps-backend's buildRuntimeFromJsFunctionWithActions is injected
into every backend function's bundle when that package is installed,
regardless of which specific function is called, and requires
$.Source.{initiator,runAsUser} to each be a User object with non-empty
id/orgId strings. Local execution's $ context never included Source,
so any app with @datadog/apps-backend installed (the create-apps scaffold
default) failed on its very first request -- not just functions that
call $.Actions, which was the only gap previously documented.
Confirmed by tracing the real, published package's bundled validation
logic and dd-source's production path: Source is populated upstream of
wf-actions-worker (the app-builder API layer, from the authenticated
caller's identity) before a bundled function ever runs -- it's plain
caller-supplied JSON by the time it reaches the script template, not a
server-signed value the runtime cross-checks. The validator only checks
shape (non-empty id/orgId strings), not a live session, so a synthetic
identity fully satisfies it.
Uses one placeholder identity for both initiator and runAsUser, since
on-behalf-of impersonation isn't meaningful when a single developer is
running their own code locally. No existing whoami-style endpoint exists
in this package's auth layer to fetch a real identity instead; real
identity resolution (if ever wanted) is a separate, non-blocking
enhancement, not a gap in this fix.
Verified against the real npm package (not a mock) via a new integration
test reusing the existing apps_backend_project fixture, and against a
real npm-linked scaffolded app calling getExecutionUser()/
getInitiatingUser() through /__dd/executeAction.
tyffical
force-pushed
the
tiffany.trinh/apps-2792-wire-local-execution
branch
from
July 29, 2026 18:14
ff7de2b to
7c9ad51
Compare
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.
Motivation
npm run devrequest path for the first time. Before this PR,local-execution.tswas fully built and tested but unreachable from any actual dev-server request.npm run devunconditionally uses local execution; the cloud round-trip becomes a separately-purposed command."Architecture
/__dd/executeActionis the customer-facing default and is now unconditionally local — no config field, no flag, per the design doc's rollout decision./__dd/executeActionViaCloudis new and additive: it preserves today's exact cloud round-trip (sameexecuteScriptViaDatadog/pollQueryExecutioncode, untouched) under a distinctly-purposed endpoint. Nonpm run dev:verifyCLI command exists yet to give customers an ergonomic way to reach it — that's now tracked as Milestone 3 in the kickoff doc, not deferred — and the existing test suite has deep coverage of this path that would otherwise be silently orphaned.Changes
/__dd/executeActionnow runs backend functions viaexecuteScriptLocally(a real forked child process) instead of the cloud round-trippackages/plugins/apps/src/vite/dev-server.ts/__dd/executeActionrequires no auth, since local execution's$.Actionscalls are stubbed today (see Follow-ups)packages/plugins/apps/src/vite/dev-server.ts/__dd/executeActionViaCloudendpoint preserves the originalexecuteScriptViaDatadogcloud round-trip byte-for-byte, including its auth gatepackages/plugins/apps/src/vite/dev-server.ts/__dd/executeActionViaCloud(the endpoint that actually needs credentials) instead of the old/__dd/executeActionnamepackages/plugins/apps/src/vite/dev-server.tskillAllLocalExecutionChildren()(from #471) into the dev server's own shutdown viaserver.httpServer'scloseeventpackages/plugins/apps/src/vite/index.ts/__dd/executeActionViaCloudinstead of/__dd/executeAction, preserving full existing coveragepackages/plugins/apps/src/vite/dev-server.test.ts/__dd/executeActionto assert against real local-execution bundle content instead of a stale nock mockpackages/plugins/apps/src/vite/dev-server.test.ts/__dd/executeActionViaCloudpackages/plugins/apps/src/vite/dev-server.test.tspackages/plugins/apps/src/vite/dev-server.test.tslocal-exec-child.jsinbuildPlugin.toBuildso it's copied into the publisheddist/srcoutput next to the compiled bundlepackages/plugins/apps/package.json$context now includes aSource: { initiator, runAsUser }object with a synthetic local-dev identity, so@datadog/apps-backend's injected runtime-context setup no longer throwspackages/plugins/apps/src/vite/local-exec-child.jsapps_backend_projectfixture (real@datadog/apps-backend) confirms a function callinggetExecutionUser()/getInitiatingUser()runs cleanly under local executionpackages/plugins/apps/src/vite/local-execution.integration.test.tsQA Instructions
yarn eslint packages/plugins/apps/src/vite/dev-server.ts packages/plugins/apps/src/vite/dev-server.test.ts packages/plugins/apps/src/vite/index.ts # Expected: no output, clean exit ✅ VERIFIEDManual QA — real dev server, both endpoints
Setup (one app covers every scenario below —
@datadog/apps-backendand@datadog/action-catalogare scaffold defaults). Three terminals are used in total: Terminal A builds this branch and is done after that; Terminal B runs the dev server and stays occupied by it; Terminal C is opened later, once the dev server is running, to send requests to it.Terminal A — folder: this repo (
~/dd/build-plugins):Terminal A isn't needed again after this — the built plugin is linked by absolute path below, not by anything left running in this terminal.
Terminal B — folder: a fresh scaffolded app outside this repo (
~/qa-app):Still Terminal B, still in
~/qa-app:Still Terminal B, still in
~/qa-app— this command blocks and keeps running, so leave it in the foreground here:# 4. Start local execution — no dd-auth, fixed port npm run dev -- --port 5180 --strictPortTerminal C — open a new terminal, folder:
~/qa-appagain (Terminal B is now occupied by the running dev server; everything below runs here instead):Local execution tests:
Timing check:
Shutdown cleanup — no orphaned child process (still Terminal C, until the Ctrl-C note below):
Switch to Terminal B (where
npm run devis still running in the foreground) and press Ctrl-C to stop it. Then back in Terminal C:pgrep -f local-exec-child.js # Expected: no output (child was killed with the dev server) ✅ VERIFIEDCloud round trip — the same
$QACHECK/$DOUBLENUMBER/$STREAMLOGSvariables from step 6 stay valid — the registered name only depends on the file's relative path, not the running server instance.Terminal B — folder: still
~/qa-app— restart with credentials, fixed port:Terminal C — folder: still
~/qa-app:Action-catalog build-time validation — no server needed, so Ctrl-C Terminal B's dev server first. Runs in either terminal, folder
~/qa-app:Blast Radius
npm run dev's/__dd/executeActionnow executes locally by default, unconditionally, per the design doc's rollout decision — no feature flag, matching the "no toggle" recommendation (Datadog's own internal canary/experiment gate, if wanted before this becomes the default for all customers, is out of scope for this PR; see design doc's Rollout section)./__dd/executeActionViaCloud) — no existing caller of the old/__dd/executeActionendpoint is silently broken without an equivalent path forward, though callers relying specifically on cloud execution at the old URL will need to switch to the new one.$.Actionscalls made during local execution remain stubbed (per [APPS-2792] Prototype: local Node execution for backend functions #461/[APPS-2792] Harden local Node execution: signal reporting, shutdown cleanup, edge-case coverage #471) — real action side effects are only available via/__dd/executeActionViaCloudtoday. This is a known, called-out gap tracked against the single-action-execution endpoint dependency, not something this PR silently papers over.@datadog/apps-backendSource-context gap found during this PR's manual QA (see above) is now fixed and re-verified against a real scaffolded app — local execution is safe as the default for apps using that package. The synthetic local-dev identity meansgetExecutionUser()/getInitiatingUser()won't reflect a real user locally (no live-session identity is available without the credentials local execution deliberately doesn't require) — acceptable for now since nothing in the design doc's scope depends on that identity being real, but worth knowing if a backend function's own logic ever branches on it.Out of Scope / Follow-ups
$.Sourceuses a synthetic local-dev identity, not a real user's —getExecutionUser()/getInitiatingUser()won't reflect real identity locallynpm run dev:verifyas a distinctly-named command for the cloud round-tripnpm run devdefaults to local execution is a real workflow regression, not just a missing nicety. Two PRs needed: mode-aware routing in this package (packages/plugins/apps/), plus acreate-appstemplate change inweb-ui(owned by@DataDog/app-builder-high-code) to add the script and an example backend function./__dd/executeActionViaCloudis reachable today via a manual curl call in the meantime.bump.yamlmajor bump +MIGRATIONS.mdentry)$.Actionsexecution during local runsDocumentation