Skip to content

fix(server): resolve Node for standalone helper scripts - #12033

Merged
juliusmarminge merged 4 commits into
mainfrom
fix/standalone-node-helpers
Sep 16, 2026
Merged

juliusmarminge merged 4 commits into
mainfrom
fix/standalone-node-helpers

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 16, 2026

Copy link
Copy Markdown
Member

Standalone builds expose the T3 executable as process.execPath. Running device helper scripts through it launches T3's embedded CLI instead of Node and leaves Device hub waiting for a process that cannot start. Antigravity's browser helper has the same assumption.

Resolve Node from PATH for standalone builds, using node.exe on Windows so batch wrappers are never launched without a shell and report Install Node.js and make sure node is on PATH, then retry before installing tools or starting helpers when it is missing. Reject symlink and hard-link aliases pointing back at T3. Preserve launcher names such as Vite+'s node alias. Use the selected runtime for hub starts and restarts, agent-device daemon starts and stops, generated agent launchers, and Antigravity's browser helper. Preserve fixed install guidance and the original cause chain through device and provider errors.

Audited every process.execPath and HostProcessExecutablePath use. Intentional T3/Claude-history relaunches, Electron workers using ELECTRON_RUN_AS_NODE, remote scripts already running under Node, and Node-only build scripts keep their existing behavior.

Validation:

  • 106 focused tests across eight files; server typecheck and targeted lint pass. Lint reports one existing array-find warning in the device handlers.
  • Built and ran the actual Node 26.8.2 single-executable server in isolated state. With Node available, hub readiness returned 200 and the app listed the Pixel, foldable, and tablet audit emulators alongside Mac Mini devices. Android SDK, Java, and the audit AVD directory were configured for this test.
  • With Node unavailable, runtime resolution failed in under 2 ms with the install message, and Mac Mini devices remained available. Before this change, the packaged executable rejects the hub flags and readiness times out.
  • Real CLI subprocess tests cover host-bound agent launchers and Antigravity browser suppression in standalone mode, plus missing-runtime and retry behavior.

Node unavailable:

Node install message alongside available Mac Mini simulators

Node available:

Device hub lists the local foldable, Pixel and tablet emulators

Found while auditing #11841.

Model: GPT-6. Harness: Codex.

Summary by CodeRabbit

  • New Features

    • Device automation and Antigravity now detect and use an available Node.js installation instead of relying on the packaged runtime.
    • Added clearer guidance when Node.js is unavailable, including installation instructions.
    • Improved support for Windows executable discovery and symlinked or linked installations.
  • Bug Fixes

    • Prevented device and Antigravity setup from attempting to use an invalid standalone application runtime.
    • Preserved underlying failure details for more reliable troubleshooting.
    • Improved error reporting for device readiness, agent startup, sign-in, and provider setup failures.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 16, 2026
Comment thread packages/shared/src/nodeRuntime.ts
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 13.6 KiB +11 B (+0.1%) 15.1 KiB
Codex Thread snapshot wire 7.1 KiB 7.0 KiB −7 B (−0.1%) 7.3 KiB
Codex Live turn WebSocket wire 6.6 KiB 6.6 KiB +18 B (+0.3%) 7.8 KiB
Codex Live turn WebSocket decoded 57.1 KiB 57.1 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 10 10 0 (0.0%) 21
Claude Total thread wire 13.6 KiB 13.6 KiB +32 B (+0.2%) 15.1 KiB
Claude Thread snapshot wire 7.1 KiB 7.1 KiB −6 B (−0.1%) 7.3 KiB
Claude Live turn WebSocket wire 6.5 KiB 6.6 KiB +38 B (+0.6%) 7.8 KiB
Claude Live turn WebSocket decoded 57.8 KiB 57.9 KiB +44 B (+0.1%) 66.4 KiB
Claude Live turn messages 9 10 +1 (+11.1%) 21

Baseline: 6ee0324 · PR result: 49fb7a0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.9 KiB
  • Claude decoded thread snapshot: 114.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment thread apps/server/src/provider/AntigravityInstallation.ts Outdated
Comment thread apps/server/src/device/LocalDeviceHost.ts Outdated
Comment thread apps/server/src/provider/antigravityAuthSupport.ts Outdated
Comment thread apps/server/src/mcp/toolkits/device/handlers.ts Outdated
Comment thread apps/server/src/device/DeviceHost.ts Outdated
Comment thread apps/server/src/device/DeviceService.ts Outdated
Comment thread apps/server/src/provider/Drivers/AntigravityDriver.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Effect Service Conventions found 7 blocking issues. See the inline review comments for required structured-error fixes.

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

macroscopeapp Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR changes executable selection and startup behavior across device and Antigravity workflows, including new preflight gates and subprocess lifecycle handling. The logic is cohesive and tested, but its cross-component runtime impact warrants human review.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 6646ace1-5b80-4b44-837f-7aa842277144

📥 Commits

Reviewing files that changed from the base of the PR and between ae39cfb and 36b0cce.

📒 Files selected for processing (12)
  • apps/server/src/device/DeviceHost.ts
  • apps/server/src/device/DeviceService.test.ts
  • apps/server/src/device/DeviceService.ts
  • apps/server/src/device/LocalDeviceHost.ts
  • apps/server/src/mcp/toolkits/device/handlers.ts
  • apps/server/src/provider/AntigravityInstallation.ts
  • apps/server/src/provider/Drivers/AntigravityDriver.ts
  • apps/server/src/provider/antigravityAuthSupport.test.ts
  • apps/server/src/provider/antigravityAuthSupport.ts
  • packages/contracts/src/device.ts
  • packages/shared/src/nodeRuntime.test.ts
  • packages/shared/src/nodeRuntime.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The pull request adds shared Node.js runtime resolution for standalone binaries. Device hosts, agent launchers, and Antigravity workflows use the resolved executable and preserve related error causes.

Changes

Node runtime integration

Layer / File(s) Summary
Runtime resolver and coverage
packages/shared/src/nodeRuntime.ts, packages/shared/src/nodeRuntime.test.ts, packages/shared/package.json
Adds standardized runtime error messages and rejects paths that resolve to the standalone application. Tests cover runtime selection, cache refresh, hard links, and symlinks.
Device runtime propagation
apps/server/src/device/*, apps/server/src/mcp/toolkits/device/handlers.ts
Device hubs, daemons, launchers, and tool paths use the resolved Node executable. The path is reused across restarts and shutdown, exposed by the ready-host API, and preserved as an error cause.
Antigravity runtime validation
apps/server/src/provider/AntigravityInstallation.ts, apps/server/src/provider/antigravityAuthSupport.ts, apps/server/src/provider/Drivers/AntigravityDriver.ts
Installation and profile preparation resolve Node before execution. Runtime failures use standardized messages, and provider refresh handling preserves setup failures from configuration and startup.
Validation and error contracts
apps/server/src/device/*.test.ts, apps/server/src/provider/**/*.test.ts, packages/contracts/src/device.ts
Tests cover unavailable Node, PATH-based selection, standalone executable handling, skipped process execution, and preserved causes. Device unavailable errors now accept defect causes.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~30 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant DeviceOrProvider
  participant resolveNodeExecutable
  participant NodeProcess
  participant ErrorSurface
  DeviceOrProvider->>resolveNodeExecutable: resolve Node executable
  resolveNodeExecutable-->>DeviceOrProvider: return executable path or runtime error
  DeviceOrProvider->>NodeProcess: run tool, hub, daemon, or browser helper
  NodeProcess-->>ErrorSurface: return result or failure cause
Loading

Merge Risk: ⚪ Minimal · up to 36b0c

The runtime-resolution changes have no identified merge-blocking issue.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Antigravity changes and shared runtime resolver support #11767. The pull request also changes device-specific behavior and contracts in AgentDeviceShim, DeviceHost, LocalDeviceHost, `DeviceS… Remove the unrelated device runtime, device error-contract, and device handler changes from this pull request, or link those changes to a separate issue with explicit coding requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #11767 requires Antigravity browser-helper preflight to use Node from PATH instead of the standalone T3 executable. prepareAntigravityProfile now calls `resolveNodeExecutable("Antigravity sign…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Title check ✅ Passed The title clearly and concisely describes the main change: resolving Node for standalone helper scripts.
Description check ✅ Passed The description explains what changed, why it changed, affected components, validation results, and UI evidence. It does not use the template headings or include the checklist, but the required substa…
Full details: Out of Scope Changes check

Explanation

The Antigravity changes and shared runtime resolver support #11767. The pull request also changes device-specific behavior and contracts in AgentDeviceShim, DeviceHost, LocalDeviceHost, DeviceService, MCP device handlers, and packages/contracts/src/device.ts. These changes resolve Node for device automation, alter device error types and messages, and add device readiness behavior. Issue #11767 only addresses Antigravity browser-helper preflight and does not establish a device scope.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/standalone-node-helpers

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

Comment thread packages/shared/src/nodeRuntime.ts Outdated
Comment thread packages/shared/src/nodeRuntime.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Effect Service Conventions found 1 blocking issue. See the inline review comment for the required bounded-error-attribute fix.

Posted via Macroscope — Effect Service Conventions

@juliusmarminge
juliusmarminge merged commit bf55408 into main Sep 16, 2026
21 checks passed
@juliusmarminge
juliusmarminge deleted the fix/standalone-node-helpers branch September 16, 2026 06:42
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 16, 2026
## What's Changed
* fix(server): detect unsupported legacy Android command-line tools by @Yash-Singh1 in pingdotgg/t3code#12017
* fix(web): stop the worktree setup card from flashing and shifting by @juliusmarminge in pingdotgg/t3code#12015
* fix(clients): show unsupported environments as neutral rows with their machine icon by @juliusmarminge in pingdotgg/t3code#12026
* fix(clients): hold the discovered machine icon across relay refreshes by @juliusmarminge in pingdotgg/t3code#12030
* fix(server): resolve Node for standalone helper scripts by @juliusmarminge in pingdotgg/t3code#12033
* feat(web): reveal timestamps on tool rows and turn folds by @saphid in pingdotgg/t3code#8641
* docs: make the standalone installer the primary way to get the CLI by @juliusmarminge in pingdotgg/t3code#11696


**Full Changelog**: pingdotgg/t3code@v0.0.41-nightly.20260916.1795...v0.0.43-nightly.20260916.1811

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.43-nightly.20260916.1811
aorwall added a commit to aorwall/t3code that referenced this pull request Sep 18, 2026
Merges `pingdotgg/t3code` `6d1d549441` into the fork, from base
`0bf2d6b010` — 50 commits.

- **Landed:** 410 files against 407 in the upstream range; the gap of 3
is `docs/fork/gaps.md`, `inventory.json` and `upstream-merge-log.md`.
Everything in the range landed.
- **Fork delta:** 777 files.
- **Verification:** all 9 `verify.mjs` checks pass, tests green in all
15 packages.
- **Unsupported methods:** ADD 0, DROP 0 —
`packages/contracts/src/rpc.ts` and `auth.ts` are untouched. Upstream
added no WebSocket method in this range.

## The one that mattered

Upstream's pingdotgg#12015 moved the **entire body of the thread route** out of
`apps/web/src/routes/_chat.$environmentId.$threadId.tsx` and into a new
upstream file, `apps/web/src/components/ThreadRouteView.tsx`, rendered
by the `_chat` layout so a draft's promotion keeps the same `ChatView`
mounted. The route file is now a seven-line stub.

Three fork deltas lived in that file. They moved with it:
`useAdoptedThread`, `useAutoFollowThread` and the
`serverThreadAwaitingFirstAnswer` argument to
`resolveThreadRouteRenderState`, all reading `target.kind === "server" ?
target.threadRef : null` — a draft's reserved ref is the viewer's own
work and the listing carries it without being asked. The
`unlisted-thread-adoption` and `thread-follow` inventory entries were
re-pointed at the new file.

The fork's own delta guard is what caught this. The merge was clean and
typecheck was green; `features.test.ts` failed because
`useAutoFollowThread` was no longer in a file the inventory said it had
to be in.

## Conflicts

8 files, each resolved with the verdict `preflight.mjs` printed. Details
in the tracker entry; the short form:

| file | verdict | resolution |
| --- | --- | --- |
| `routes/_chat.$environmentId.$threadId.tsx` | unlisted | took
upstream's stub, deltas relocated (above) |
| `chat/MessagesTimeline.tsx` | `message-origin-upstream-files` | both
sides of `TimelineRowActivityState`, its memo and its deps merged;
dropped upstream's now-unused `GitPullRequestIcon` |
| `ThreadStatusIndicators.tsx` | `thread-status-indicators` | fork's
memo above upstream's early return — hooks before any conditional
`return null` |
| `settings/ProviderInstanceCard.tsx` | unlisted, in
`moatless-provider-auth` | kept the `FEATURES.providerConfiguration`
ternary, took upstream's container-query classNames inside it |
| `settings/SettingsPanels.tsx` | `settings-surface-gates` | re-stated
the fork's browser clause onto upstream's rewritten `proactive-panels`
text |
| `BranchToolbar.tsx` | `branch-toolbar-gates` | import block, both
sides kept |
| `RightPanelTabs.tsx` | `right-panel-surfaces` | import block, both
sides kept |
| `pnpm-lock.yaml` | `theirs — lockfile` | `--theirs` then `vp i`,
re-derived lockfile committed |

## Path policy closed a hole

`resolution-check` listed eight unlisted paths both sides changed;
**seven carried a real fork delta**, so next merge's `theirs` fallback
would have dropped them silently. All seven are now listed — five new
entries (`command-palette-gates`, `diff-panel-gates`,
`provider-settings-gates`, `chat-layout-route`,
`client-runtime-exports`) plus `rightPanelStore.test.ts` added to
`right-panel-surfaces`. The eighth is the thread route stub, which
resolved to upstream byte for byte.

## Usable as-is

Client work that runs against the Moatless backend today:

- **pingdotgg#12015** worktree setup card no longer flashes or shifts (the
relocation above) · **pingdotgg#12144** thread reading positions are preserved ·
**pingdotgg#12162** header spacing stays stable when the sidebar drawer opens
- **pingdotgg#8641** timestamps on tool rows and turn folds · **pingdotgg#12152** those
timestamps sit before the disclosure chevron · **pingdotgg#12147** thoughts group
into the changing tool activity line
- **pingdotgg#12075** send-shortcut and follow-up controls · **pingdotgg#12160** rich text
composer on by default · **pingdotgg#12165** composer task rows aligned ·
**pingdotgg#11787** tooltips on the composer's environment and workspace controls
· **pingdotgg#12082** simpler agent approval prompts
- **pingdotgg#12139** diff panel defaults to the working tree · **pingdotgg#12190** diff
files collapse by default · **pingdotgg#12142** a linked pull request wins over
an automatic diff
- **pingdotgg#12143** themes picked from chat with colour previews · **pingdotgg#12138**
provider settings adapt to content width · **pingdotgg#12167** follow-up and
license controls aligned
- **pingdotgg#12026** unsupported environments render as neutral rows with their
machine icon · **pingdotgg#12030** a discovered machine's icon survives a relay
refresh · **pingdotgg#12001** dropped folders become path chips locally and are
refused on remote environments
- **pingdotgg#11144** pull-request icon state centralised — a refactor the fork's
own badge filtering now rides

Not fork surfaces, landed for completeness: the mobile work (pingdotgg#11841,
pingdotgg#12169, pingdotgg#12177, version bump), the CLI installer progress bar (pingdotgg#12044),
docs (pingdotgg#11696), release chores and the Fable 5.1 badge (pingdotgg#12173).

## Unsupported in Moatless / needs implementation

- **Pull request surface** — `FEATURES.pullRequestSurface` is off, so
none of this merge's pull-request work is reachable: **pingdotgg#11994** (submit
PR comments with Cmd/Ctrl+Enter), **pingdotgg#12150** (comments easier to scan,
`apps/web/src/components/pullRequest/**` plus a `pullRequest.ts`
contract field), **pingdotgg#12168** (cached GitHub PR details reused across
entry points), **pingdotgg#12125** and **pingdotgg#11728** (author avatars and their
fallback). **pingdotgg#11706** needs backend work on top: private-repository
media in PR tabs goes through a new `packages/contracts/src/assets.ts`
proxy that Moatless would have to serve. Opening the surface means
deleting the `pullRequestSurface` entry and its gates, and dispatching
`pullRequests.list` / `.detail` / `.activity` — only
`pullRequests.summary` is served today.
- **Keybindings settings page** — **pingdotgg#12175** turns every keybinding
command into a searchable settings row pointing at
`/settings/keybindings`, which `FEATURES.serverAdministration` keeps out
of the sidebar and redirects on a typed URL. The rows still match in
settings search and land on that redirect. Left as-is this merge — it is
the same shape as the six `snap-shot-*` rows that have always done this,
and the one-line fix (a `settingsPathEnabled(item.to)` filter in
`filterAvailableSettingsSearchItems`) is a behaviour change that belongs
outside a merge. Recorded in `gaps.md`. Closes properly when
`server.upsertKeybinding` / `removeKeybinding` are dispatched.
- **Device hub** — **pingdotgg#12017** (detect unsupported legacy Android
command-line tools) and **pingdotgg#12033** (resolve Node for standalone helper
scripts) are both `apps/server/src/device/**`. `FEATURES.deviceHub` is
off and Moatless runs no device host at all, so there is nothing to do
and nothing to reproduce.

## Backend behavior to consider reproducing in Moatless

All recorded in `docs/fork/gaps.md`; nothing in this repository holds
them open.

Checkpoint and turn path, under _Runtime fixes upstream made to its own
server_:

- **pingdotgg#12154** keep large sparse checkouts on the fast checkpoint path —
streams `git ls-files --full-name --sparse -z -v` under a 4 KiB cap and
pins `sparse.expectFilesOutsideOfPatterns=false`. Without it a sparse
checkout large enough to blow the output limit drops to the slow path on
every checkpoint.
- **pingdotgg#10944** flush checkpoint objects and refs before publishing them —
otherwise a reader that acts on the announcement can find a ref pointing
at an object that is not there yet. Rare, unreproducible, permanent when
it lands.
- **pingdotgg#8432** keep a ready checkpoint when a later placeholder arrives
(`ProjectionPipeline.ts`) — the symptom is a checkpoint reverting to
pending and never coming back.
- **pingdotgg#11970** keep VCS waits from blocking turn completion
(`ProviderRuntimeIngestion.ts`, `decider.ts`) — a slow git call between
the provider's last event and the turn being marked done. Slower in a
sandbox than upstream.

Settlement, under _Settlement rules Moatless owns_:

- **pingdotgg#12161** settle on the `thread.pull-request-linked` / `-synced`
event with a per-thread sweep rather than waiting for the next periodic
one.
- **pingdotgg#12176** make the cancellation path uninterruptible around
record-and-rollback, so a cancelled worktree setup records its
settlement instead of being left mid-setup.

Client features that are inert until the backend emits or honours
something:

- **pingdotgg#11784** provider thinking traces — `orchestration` gained a
`reasoning` message role and `thread.message.reasoning.delta` /
`.complete` commands behind a `reasoningMessages: true` opt-in on
subscribe. The client renders them when they arrive; Moatless emits
none, so there are no traces.
- **pingdotgg#10822** complete counts and progressive large diffs —
`review.getDiffPreview` gained an optional `file` input (one file's
patch) and an optional `files` stat array ("absent on older servers").
Moatless dispatches the method and honours neither, so large diffs stay
truncated with incomplete counts.
- **pingdotgg#11519** native provider slash commands, exposed server-side and
consumed by the mobile client.
- **pingdotgg#12115** OpenCode Go, Cursor and Grok subscription limits in the
usage scan.

## Verification

`tripwires`, `duplicate-adds`, `resolution-check`, `inventory-check`,
`unsupported-methods`, `lockfile`, `fmt:check`, `lint` and `typecheck`
all pass; tests pass in all 15 packages. Two failures were found and
fixed on the way:

- `TS2552: Cannot find name 'label'` in `ThreadStatusIndicators.tsx` —
pingdotgg#11104/pingdotgg#11180 hoisted `label` onto the presentation object and the
fork's multi-link popover branch still read the removed local.
- The delta-guard test failure described above.

Two operational notes for the next run are in the tracker entry: `vp i`
needs `NODE_OPTIONS=--max-old-space-size=6144` in this sandbox, and
`--force-with-lease` needs the explicit `<ref>:<sha>` form with the SHA
read from `git ls-remote`, because this clone only fetches `main` and
the branch has no lease-eligible tracking ref.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/e3e17736-1c3d-4873-b9af-c434fd31b003
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant