Repository navigation
[APPS-2961] Update: run each local backend function call in a fresh process - #608
Conversation
Live Debugger Runtime BenchmarkSDK-loaded dormant-probe runtime overhead, measured against an uninstrumented bundle in the same browser session.
What do the Tiny and Hot workloads represent? Browser Debugger SDK: Full diagnosticsRaw samples are in the |
b0d7fe4 to
5bb05d3
Compare
5bb05d3 to
4c9b050
Compare
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
🟡 Changes recommended
Shutdown cleanup can leave children running, and linked-package dependencies outside the app’s workspace can be incorrectly blocked.
2 open issues
What changed in this PR
Moves Vite 6+ local backend calls into fresh child processes, improving production parity while retaining Vite 5’s in-process fallback. Parallel execution remains a follow-up.
Changes:
- Adds process isolation, permissions, timeouts, and streamed logs.
- Routes module loading and authenticated actions through the dev server.
- Adds integration coverage, fixtures, packaging, and documentation.
| File | Description |
|---|---|
| yarn.lock | Locks new matching dependencies. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childVirtual.backend.ts | Tests virtual module imports. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childTopLevelWriteHelper.ts | Probes load-time writes. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childTopLevelWrite.backend.ts | Exercises load-time guards. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childState.backend.ts | Exercises isolation, limits, and logs. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childSlowLoad.backend.ts | Exercises loading timeouts. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childSandboxProbes.ts | Probes permissions and module requests. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childSandbox.backend.ts | Exposes sandbox test functions. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childLoadTime.backend.ts | Exercises runtime-context initialization. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childLaterImport.backend.ts | Exercises missing imports. |
| packages/tests/src/_jest/fixtures/apps_backend_project/childActions.backend.ts | Exercises action validation. |
| packages/plugins/apps/src/vite/local-execution.ts | Exports the shared execution queue. |
| packages/plugins/apps/src/vite/local-execution-protocol.ts | Defines parent-child messages. |
| packages/plugins/apps/src/vite/local-execution-process.ts | Implements child orchestration and checks. |
| packages/plugins/apps/src/vite/local-execution-process.test.ts | Covers child execution behavior. |
| packages/plugins/apps/src/vite/local-execution-process.fixtures.ts | Builds the test child entry. |
| packages/plugins/apps/src/vite/local-execution-child.ts | Executes functions and forwards actions. |
| packages/plugins/apps/src/vite/local-execution-child-path.ts | Locates the bundled child entry. |
| packages/plugins/apps/src/vite/index.ts | Selects and configures the executor. |
| packages/plugins/apps/src/vite/index.test.ts | Tests executor selection and fallback. |
| packages/plugins/apps/src/vite/dev-server.ts | Delegates local calls to the executor. |
| packages/plugins/apps/src/vite/dev-server.test.ts | Tests executor delegation. |
| packages/plugins/apps/src/vite/dev-server.integration.test.ts | Verifies fresh processes through middleware. |
| packages/plugins/apps/src/vite/dev-server-module-graph.ts | Makes stale-module uncaching configurable. |
| packages/plugins/apps/src/vite/dev-server-module-graph.test.ts | Tests child-mode cache handling. |
| packages/plugins/apps/README.md | Documents isolation, limits, and debugging. |
| packages/plugins/apps/package.json | Adds child packaging and matching dependencies. |
Review effort: Balanced
aa5b6d7 to
ba6b653
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba6b65359a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ba6b653 to
4d1fb34
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
| env, | ||
| stdio: ['ignore', 'ignore', 'ignore', 'ipc'], |
There was a problem hiding this comment.
The reaper process is no longer in this PR, so this doesn't apply. A call stuck in a synchronous loop after its dev server dies without closing is listed under Out of Scope.
| } | ||
|
|
||
| const writeToTerminal = (stream: OutputStream, line: string) => { | ||
| process[stream].write(`${line}\n`); |
There was a problem hiding this comment.
The 64 KiB cap is no longer in this PR, but forwarding still doesn't apply backpressure. That's tracked under Out of Scope instead of fixed here.
On Vite 6.0.4+, /__dd/executeAction forks a Node process per call, running a Vite module runner that loads modules from the dev server over IPC. Each call starts with fresh module state. Calls still run one at a time. Each process runs under the in-process guards, which refuse writes outside its temp dir and /tmp, subprocesses and worker threads. Its heap is capped at 256 MB and its environment holds no developer variables or keys. $.Actions calls go back to the dev server, which keeps the credentials and checks each call's connection. Output streams to the terminal line by line. A call past its time budget is killed, even in a synchronous loop, and the dev server kills its calls when it closes, restarts or exits. Vite 5, Vite 6.0.0 to 6.0.3, whose dev server answers the module runner in an older shape, and Yarn Plug'n'Play projects, whose packages load through hooks the call process doesn't get, keep the in-process path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4d1fb34 to
4384bc9
Compare


Motivation
Architecture
sequenceDiagram participant B as "Browser" participant D as "Vite dev server" participant C as "Call process" participant API as "Datadog API" B->>D: "POST /__dd/executeAction" D->>API: "Hydrate $ (preview-async)" D->>C: "Fork a fresh process: stripped env, 256 MB heap" loop "Each module the call imports" C->>D: "fetchModule over IPC" D-->>C: "Module code" end C->>D: "$.Actions call" D->>D: "Allowlist check, add credentials" D->>API: "Single-action query" D-->>C: "action-result" C-->>D: "stdout and stderr lines" C->>D: "result or error" D->>C: "SIGKILL" D-->>B: "Response"Changes
7 changes across local-execution-process.ts, local-execution-child.ts, index.ts, the README and tests
local-execution-process.ts$.Actionsfor the call over IPC.local-execution-process.tslocal-execution-process.ts,index.tsModuleRunnerunder the in-process guards.local-execution-child.ts,local-execution-protocol.tsindex.ts,dev-server.ts,dev-server-module-graph.tspackage.json,local-execution.ts,README.mdlocal-execution-process.integration.test.ts,local-execution-process.test.ts,dev-server.integration.test.ts,index.test.tsQA Instructions
Manual QA in a Vite 8 app against a Datadog staging org
1, 1, 1DD_*variables a dependency sees$.Actionsunscoped, unlisted connectiondev:verify{ "label": "after", "freshState": [ 1, 1, 1 ], "oneCallMs": 1940, "fourParallelCallsMs": 7074, "parallelPids": [ 992, 1023, 1029, 1038 ], "timeout": { "status": 500, "ms": 10797, "body": { "success": false, "error": "Local execution of \"waitForever\" timed out after 10000ms" } }, "syncLoopTimeout": { "status": 500, "ms": 10813, "body": { "success": false, "error": "Local execution of \"spinForever\" timed out after 10000ms" } }, "sandbox": { "success": true, "result": { "data": { "projectWrite": "refused: EROFS", "tempWrite": "ok: x", "spawn": "refused: Spawning a subprocess is not allowed in backend functions.", "envKeys": "none" } } }, "projectFileExists": false, "logs": { "result": { "success": true, "result": { "data": "logged" } }, "lines": [ { "msAfterCallStart": 738, "line": "qa first line" }, { "msAfterCallStart": 2240, "line": "qa second line" } ], "callMs": 2243 }, "actionWithoutConnection": { "status": 200, "ms": 1204, "body": { "success": true, "result": { "data": { "type": "object", "keys": [ "body", "headers", "status" ] } } } }, "actionWithUnlistedConnection": { "status": 500, "ms": 825, "body": { "success": false, "error": "Action $.Actions.http.request used connection \"qa-unlisted-connection\", which is not in this function's allowed connections: []" } } }nodeLinker: pnp, run withcorepack yarn vite:Blast Radius
/__dd/executeActionon Vite 6.0.4+; builds, deploys anddev:verifyare unchanged.Out of Scope / Follow-ups
3 items deferred
kill -9.Documentation
ModuleRunnerandhandleInvoke🤖 Generated with Claude Code