Skip to content

Harden packaged runtime startup#299

Merged
zvadaadam merged 8 commits into
mainfrom
zvadaadam/fix-github-link-poll-cancel
May 20, 2026
Merged

Harden packaged runtime startup#299
zvadaadam merged 8 commits into
mainfrom
zvadaadam/fix-github-link-poll-cancel

Conversation

@zvadaadam

@zvadaadam zvadaadam commented May 20, 2026

Copy link
Copy Markdown
Owner

Harden packaged runtime startup by routing packaged backend, AAP, and device-use children through bundled runtime env, fixing ESM native module loading via createRequire, and keeping dev agent CLI staging from rewriting the full packaged manifest.

Bundle and verify device-use simulator helpers plus Mobile Use payloads in macOS artifacts, including universal helper checks and packaged runtime smokes that exercise backend commands, agent discovery, and device-use serve.

Document runtime/agent CLI ownership boundaries and add focused tests for AAP launch routing and packaged runtime smoke invariants.

Verification: git diff --check; pre-commit ran bun install, eslint --fix, and prettier --write.

Summary by CodeRabbit

  • New Features

    • Added device-use runtime command with simulator tooling, serve passthrough args, and packaged simulator helpers
    • Runtime CLI supports passthrough args and improved packaged entry resolution
  • Bug Fixes

    • Prefetch flow now skips missing entrypoints before spawning
  • Tests

    • Expanded integration and smoke tests to cover prefetch, device-use, and packaged-runtime scenarios
  • Documentation

    • Added runtime & agent CLI ownership documentation
  • Chores

    • Build/dev scripts, packaging and smoke checks updated to stage/verify agent CLIs and simulator helpers

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds device-use runtime command support, centralizes PACKAGED_SYSTEM_PATHS, switches backend dynamic imports to createRequire for ESM compatibility, adds AAP prefetch entrypoint validation and launch command resolution for device-use, makes device-use build/staging idempotent with native helper handling, creates shared device-use payload helpers, and extends packaged/source runtime smoke tests.

Changes

Device-Use Integration

Layer / File(s) Summary
Shared Runtime Constants
shared/runtime.ts, apps/desktop/main/*, shared/lib/cli-path.ts
Export and consolidate PACKAGED_SYSTEM_PATHS across desktop, backend, and CLI path modules.
Backend ESM Module Loading
apps/backend/src/lib/sqlite.ts, apps/backend/src/services/pty.service.ts
Load better-sqlite3, bun:sqlite, and node-pty via createRequire(import.meta.url)-based requireModule to be ESM-compatible.
Simulator Command Refactoring
apps/backend/src/services/agent/commands.ts
Introduce execFileAsync (promisified) and switch simulator handlers to use it for xcrun simctl invocations.
AAP Prefetch Validation & Device-Use Launch Command
apps/backend/src/services/aap/apps.service.ts, apps/backend/src/services/aap/lifecycle.ts, apps/backend/test/*
Compute resolved prefetch args earlier and skip prefetch when a path-like first arg entrypoint is missing; add resolveLaunchCommand to optionally swap spawn command to DEUS_RUNTIME_EXECUTABLE and inject ["device-use"] argsPrefix for device-use. Unit and integration tests updated.
Runtime Device-Use Command Support
apps/runtime/index.ts
Add device-use CLI parsing with passthroughArgs, inspect simulator helper binaries, set DEVICE_USE_* env vars, resolve packaged device-use CLI entry, rewrite process.argv and dynamically import the packaged CLI, and extend self-test to report simulator helper state.
Device-Use Build Pipeline
packages/device-use/scripts/build-ts.ts, packages/device-use/src/cli/commands/serve.ts, packages/device-use/native/Sources/SimInspector/build.sh, scripts/prepare-device-use.mjs
Add server build entry, rewrite built CLI shebang and mark executable, set siminspector install-name at link time, and make prepare-device-use idempotent with staged-helper detection, permission preservation, and install-name normalization.
Device-Use Payload Constants & Verification
scripts/runtime/lib/device-use-payloads.cjs
Create shared constants and helpers for device-use packaged payload paths, helper names, and install-name assertions used by packaging and smoke checks.
Packaging beforePack Checks
scripts/runtime/electron-builder-before-pack.cjs
Assert device-use payload presence/universality/executability and run install-name checks on darwin before packaging.
Smoke Testing Infrastructure
scripts/runtime/smoke-packaged-app.cjs, scripts/runtime/smoke-packaged-runtime.cjs, scripts/runtime/smoke-source-runtime.cjs, test/unit/runtime/smoke-packaged-runtime.test.ts, electron-builder.yml
Validate device-use payloads during beforePack, add universal-binary/install-name/helper checks to packaged-app smoke, add device-use serve smoke (ephemeral port, WS q:command, HTTP AAP flows, workspace creation, app launch/health), add source runtime device-use --version smoke, include simbridge/siminspector in mac packaging, and add a unit smoke test ensuring packaged runtime doesn't load better-sqlite3.
Agent CLI Platform-Specific Staging
scripts/runtime/prepare-dev-agent-clis.ts, scripts/runtime/agent-clis.ts
Add prepare-dev-agent-clis to stage only matching host runtimes and extend PrepareAgentCliOptions with runtimeKeys and writeManifest to control staging and manifest writes.
Build & Development Configuration
package.json, scripts/runtime/dev.ts, scripts/runtime/rebuild-node-native.cjs
Run prepare-dev-agent-clis during dev startup, conditionally build agent-server when missing, include prepare:device-use in build:all, add rebuild-node-native.cjs to rebuild better-sqlite3 native bindings, and support NODE_EXECUTABLE for dev backend spawns.
Integration & Unit Tests
apps/backend/test/integration/aap.test.ts, apps/backend/test/unit/services/aap/lifecycle.test.ts, test/unit/runtime/smoke-packaged-runtime.test.ts
Extend AAP prefetch tests (URL-prefetch and missing-command manifest), add lifecycle unit tests for resolveLaunchCommand, and add a packaged-runtime smoke unit test.
Ownership & Build Documentation
docs/runtime-agent-cli-ownership.md
Document ownership, verification responsibilities, cleanup boundaries, deferrals, and applied cleanup for runtime packaging, agent CLI discovery, and device-use payload readiness.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hopping through ESM and build-time trails,

I stage helpers, check binaries, and mend failing sails.
I swap commands, rewrite argv with care,
And smoke-test the runtime with a curious stare.
Cheers — a bunny’s patch, light as air.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Harden packaged runtime startup' clearly and concisely summarizes the primary objective of the changeset: improving the robustness of packaged runtime initialization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zvadaadam/fix-github-link-poll-cancel

Comment @coderabbitai help to get the list of available commands and usage tips.

…link-poll-cancel

# Conflicts:
#	apps/backend/src/lib/sqlite.ts
#	apps/backend/src/services/agent/commands.ts
#	apps/backend/test/unit/runtime/agent-process.test.ts
#	package.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
scripts/runtime/smoke-packaged-runtime.cjs (1)

99-105: 💤 Low value

Shell injection vector in isCliAvailable.

While JSON.stringify escapes quotes, it doesn't prevent all shell metacharacters. If command contains characters like $(...) or backticks, they could still be interpreted. Since this is an internal smoke test and the only caller passes the literal string "xcrun", this is low risk but worth tightening.

🛡️ Safer alternative using direct exec
 function isCliAvailable(command) {
-  const result = spawnSync("sh", ["-c", `command -v ${JSON.stringify(command)}`], {
+  const result = spawnSync("command", ["-v", command], {
+    shell: true,
     stdio: "ignore",
     timeout: 2_000,
   });
   return result.status === 0;
 }

Or use which directly:

 function isCliAvailable(command) {
-  const result = spawnSync("sh", ["-c", `command -v ${JSON.stringify(command)}`], {
+  const result = spawnSync("which", [command], {
     stdio: "ignore",
     timeout: 2_000,
   });
   return result.status === 0;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/runtime/smoke-packaged-runtime.cjs` around lines 99 - 105, The
isCliAvailable function uses spawnSync with a shell string that can interpret
metacharacters, creating a shell injection risk; fix it by avoiding the shell:
call the executable directly (e.g., use spawnSync("which", [command], {...}) or
spawnSync with an exec-file style argument array) or, if you must use a
shellless builtin, validate/sanitize command against a strict whitelist/regex
(e.g., only allow [A-Za-z0-9._-]) before invoking; update isCliAvailable to use
the direct exec call or whitelist check so no user-controlled data is
interpolated into a shell command.
scripts/runtime/agent-clis.ts (1)

632-636: ⚡ Quick win

Validate runtimeKeys input to avoid silent no-op staging.

If callers pass an unknown key, this currently skips all targets and still reports success. Add an upfront validation against AGENT_CLI_TARGETS and throw on unsupported keys.

Proposed patch
   const manifestTargets: StagedAgentCli[] = [];
   const runtimeKeys = options.runtimeKeys ? new Set(options.runtimeKeys) : null;
+  if (runtimeKeys) {
+    const supported = new Set(AGENT_CLI_TARGETS.map((t) => t.runtimeKey));
+    const invalid = [...runtimeKeys].filter((key) => !supported.has(key));
+    if (invalid.length > 0) {
+      throw new Error(`Unsupported agent CLI runtime key(s): ${invalid.join(", ")}`);
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/runtime/agent-clis.ts` around lines 632 - 636, Validate the provided
runtime keys before the loop: check options.runtimeKeys (and the derived
runtimeKeys Set) against the known AGENT_CLI_TARGETS list and throw an error if
any unknown key is present so we don't silently skip all targets; implement this
by computing the set of allowed runtime keys from AGENT_CLI_TARGETS, finding any
difference with options.runtimeKeys, and throwing a descriptive exception that
names the unsupported keys (referencing options.runtimeKeys, runtimeKeys, and
AGENT_CLI_TARGETS to locate the change).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/runtime/agent-clis.ts`:
- Around line 632-636: Validate the provided runtime keys before the loop: check
options.runtimeKeys (and the derived runtimeKeys Set) against the known
AGENT_CLI_TARGETS list and throw an error if any unknown key is present so we
don't silently skip all targets; implement this by computing the set of allowed
runtime keys from AGENT_CLI_TARGETS, finding any difference with
options.runtimeKeys, and throwing a descriptive exception that names the
unsupported keys (referencing options.runtimeKeys, runtimeKeys, and
AGENT_CLI_TARGETS to locate the change).

In `@scripts/runtime/smoke-packaged-runtime.cjs`:
- Around line 99-105: The isCliAvailable function uses spawnSync with a shell
string that can interpret metacharacters, creating a shell injection risk; fix
it by avoiding the shell: call the executable directly (e.g., use
spawnSync("which", [command], {...}) or spawnSync with an exec-file style
argument array) or, if you must use a shellless builtin, validate/sanitize
command against a strict whitelist/regex (e.g., only allow [A-Za-z0-9._-])
before invoking; update isCliAvailable to use the direct exec call or whitelist
check so no user-controlled data is interpolated into a shell command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 91b9c539-9353-433f-a8c6-b370f736fdb9

📥 Commits

Reviewing files that changed from the base of the PR and between 589a170 and ffac562.

📒 Files selected for processing (30)
  • apps/backend/src/lib/sqlite.ts
  • apps/backend/src/services/aap/apps.service.ts
  • apps/backend/src/services/aap/lifecycle.ts
  • apps/backend/src/services/agent/commands.ts
  • apps/backend/src/services/pty.service.ts
  • apps/backend/test/integration/aap.test.ts
  • apps/backend/test/unit/runtime/agent-process.test.ts
  • apps/backend/test/unit/services/aap/lifecycle.test.ts
  • apps/desktop/main/backend-process.ts
  • apps/desktop/main/cli-tools.ts
  • apps/desktop/main/runtime-env.ts
  • apps/runtime/index.ts
  • docs/runtime-agent-cli-ownership.md
  • electron-builder.yml
  • package.json
  • packages/device-use/native/Sources/SimInspector/build.sh
  • packages/device-use/scripts/build-ts.ts
  • packages/device-use/src/cli/commands/serve.ts
  • scripts/prepare-device-use.mjs
  • scripts/runtime/agent-clis.ts
  • scripts/runtime/dev.ts
  • scripts/runtime/electron-builder-before-pack.cjs
  • scripts/runtime/lib/device-use-payloads.cjs
  • scripts/runtime/prepare-dev-agent-clis.ts
  • scripts/runtime/smoke-packaged-app.cjs
  • scripts/runtime/smoke-packaged-runtime.cjs
  • scripts/runtime/smoke-source-runtime.cjs
  • shared/lib/cli-path.ts
  • shared/runtime.ts
  • test/unit/runtime/smoke-packaged-runtime.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2724d695c

ℹ️ 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".

const rawCwd = prefetch.cwd ? substituteTemplate(prefetch.cwd, vars) : packageRoot;
const cwd = isAbsolute(rawCwd) ? rawCwd : resolvePath(packageRoot, rawCwd);
const command = resolveCommand(prefetch.command, packageRoot);
const args = substituteArgs(prefetch.args, vars);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move prefetch arg templating after spawnability guard

runPrefetch now calls substituteArgs before checking canSpawnResolvedCommand, so a manifest with templated prefetch args (for example {workspace}) will throw immediately when vars is empty even in the “command unavailable” path that is supposed to be skipped. Because prefetchInstalledAppAssets() fire-and-forgets runPrefetch, this rejection is unhandled and can crash startup under Node’s default unhandled-rejection behavior instead of logging a benign skip.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d744949: prefetch now checks whether the resolved command is spawnable before expanding cwd/args/env templates, so unavailable optional prefetch commands skip cleanly instead of rejecting on empty boot-time vars. Added integration coverage for a missing prefetch command with templated cwd/args.

Comment on lines +249 to +252
if (!firstArg.includes("/") && !firstArg.includes("\\")) return null;

const entrypoint = isAbsolute(firstArg) ? firstArg : resolvePath(cwd, firstArg);
return existsSync(entrypoint) ? null : entrypoint;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude URL-like args from prefetch entrypoint checks

The new missing-entrypoint guard treats any first arg containing / or \ as a filesystem path, so valid commands like curl https://... (or any tool whose first operand is a URI) are incorrectly resolved against cwd and skipped as “entrypoint unavailable.” This silently disables legitimate prefetch commands for manifests that use URL-style operands.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d744949: the prefetch entrypoint guard now ignores URI-like first operands such as https://... and file:..., so valid URL operands are not treated as missing filesystem paths. Added an integration case that runs a prefetch command with an https:// first arg.

Comment thread package.json Outdated
"postinstall": "bun run prepare:device-use",
"native:electron": "electron-builder install-app-deps",
"native:node": "cd node_modules/better-sqlite3 && node ../node-gyp/bin/node-gyp.js rebuild",
"native:node": "cd node_modules/better-sqlite3 && PYTHON=${PYTHON:-/usr/bin/python3} node ../node-gyp/bin/node-gyp.js rebuild",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid forcing PYTHON to /usr/bin/python3

Setting PYTHON=${PYTHON:-/usr/bin/python3} hardcodes a fallback path that does not exist on some supported developer/CI setups where Python is installed elsewhere (for example via non-system toolchains). In those environments node-gyp fails immediately, breaking bun run native:node (and commands that depend on it, like dev:web and backend tests) even though a usable python3 is on PATH.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d744949: native:node now uses scripts/runtime/rebuild-node-native.cjs, which validates available Python candidates instead of forcing PYTHON=/usr/bin/python3. It still handles local broken Python installs by selecting a working candidate, and falls back to node-gyp discovery if none is validated.

@zvadaadam

Copy link
Copy Markdown
Owner Author

Greenlight — Round 2

Fixed 3 review comments in d744949:

  • Moved AAP prefetch template expansion after the spawnability guard for unavailable optional commands.
  • Excluded URI-like first operands from the missing-entrypoint prefetch guard.
  • Replaced the hardcoded native:node Python fallback with a validated Python selector script.

Validation: bun run native:node, focused AAP tests, bun run typecheck, bun run test:backend.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d744949f56

ℹ️ 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".

Comment on lines +251 to +254
if (!firstArg.includes("/") && !firstArg.includes("\\")) return null;

const entrypoint = isAbsolute(firstArg) ? firstArg : resolvePath(cwd, firstArg);
return existsSync(entrypoint) ? null : entrypoint;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exempt package specifiers from prefetch path checks

Treating any first arg containing / or \\ as a filesystem entrypoint now breaks valid prefetch commands like bunx @scope/tool or npx @scope/tool, because scoped package names include / but are not paths. In those cases this guard resolves @scope/tool against cwd, marks it missing, and skips prefetch with entrypoint unavailable, so legitimate third-party prefetch flows never run.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e70ab8f. The prefetch missing-entrypoint guard now only treats explicit filesystem entrypoints as paths (absolute paths, ./, ../, Windows drive paths), so scoped package arguments like @scope/tool are passed through. Added integration coverage for a scoped package-style prefetch argument.

@zvadaadam

Copy link
Copy Markdown
Owner Author

Greenlight update

Fixed the latest review round in e70ab8f:

  • Forced package:mac:dir to refresh device-use payloads before builder runs.
  • Added device-use source/output freshness validation in before-pack.
  • Narrowed packaged device-use runtime redirection to the actual device-use package root.
  • Fixed AAP prefetch path detection so scoped package args like @scope/tool are not mistaken for missing filesystem entrypoints.

Validated locally with typecheck, targeted backend AAP tests, runtime rebuild, package:mac:dir -- --arch arm64, and packaged app/runtime smokes.

@zvadaadam zvadaadam merged commit e02c06c into main May 20, 2026
7 checks passed
zvadaadam added a commit that referenced this pull request May 31, 2026
* Harden packaged runtime startup

* Fix device-use runtime CLI import

* Address device-use packaging review

* Address AAP prefetch review

* Tighten AAP runtime packaging guards

* chore: organize runtime smoke scripts

* chore: document runtime package flow
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