Skip to content

Fix supervised/local regressions - #1020

Merged
RhysSullivan merged 6 commits into
mainfrom
codex/fix-supervised-local-regressions
Jun 15, 2026
Merged

Fix supervised/local regressions#1020
RhysSullivan merged 6 commits into
mainfrom
codex/fix-supervised-local-regressions

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

Fixes regressions and adds e2e coverage.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 1e79869 Jun 15 2026, 02:26 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 1e79869 Commit Preview URL

Branch Preview URL
Jun 15 2026, 02:25 AM

@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Jun 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1020

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1020

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1020

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1020

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1020

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1020

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1020

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1020

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1020

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1020

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1020

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1020

executor

npm i https://pkg.pr.new/executor@1020

commit: 1e79869

@RhysSullivan
RhysSullivan force-pushed the codex/fix-supervised-local-regressions branch from ff15559 to 76b8a33 Compare June 15, 2026 01:26
@RhysSullivan
RhysSullivan marked this pull request as ready for review June 15, 2026 02:36
@RhysSullivan
RhysSullivan merged commit aa148a2 into main Jun 15, 2026
14 checks passed
@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two categories of regressions: (1) the desktop supervised-daemon path (launchctl enable-before-bootstrap ordering, executable-path drift detection, version-upgrade auto-replace) and (2) the local/CLI web transition flows (service install takeover, systemd quoting, loopback binding). The desktop build pipeline is also simplified — the old bespoke bun build --compile sidecar is replaced by reusing the same compiled executor CLI binary that npm installs use.

  • Desktop service management now fully delegates to the bundled executor CLI binary (executor install / service uninstall / restart) instead of driving launchctl directly, eliminating ~150 lines of duplicated plist generation and fixing the "Bootstrap failed: 5" regression caused by enable running after bootstrap.
  • planServiceInstall (new pure function in apps/cli/src/daemon.ts) centralises all service-install decision logic with exhaustive unit tests covering noop, reinstall, and takeover-then-install paths.
  • MCP browser-approval ownership is now enforced: selfhost passes the resolved Principal to handlePausedRequest/handleApprovalRequest, preventing cross-user approval access; covered by a new selfhost e2e scenario.

Confidence Score: 4/5

Safe to merge — the core regression fixes are well-tested and the logic is correct; two spots in the React connection menu deserve a follow-up.

The ServerConnectionMenu hydration path introduces an async await inside a then-callback but only checks the cancellation flag before the await, leaving persistSnapshot and state setters reachable after unmount. Additionally, hasDesktopServerConnectionBridge is defined twice with subtly different predicates, which could produce inconsistent desktop-mode behaviour if the bridge APIs are ever partially present. Everything else — service install planning, launchctl fix, CLI delegation, MCP ownership — looks solid.

packages/app/src/web/server-connection-menu.tsx (missing post-await cancellation check, divergent bridge helper) and packages/react/src/api/server-connection.tsx (silent no-op guard)

Important Files Changed

Filename Overview
apps/cli/src/daemon.ts Adds exported pure planServiceInstall function for deciding noop/reinstall/takeover; logic is correct and exhaustively unit-tested.
apps/cli/src/service.ts Fixes critical launchctl regression (enable before bootstrap), adds proper systemd quoting, adds --hostname 127.0.0.1 to service args, and passes through Sentry env vars — all changes are correct.
apps/desktop/src/main/index.ts Replaces manual launchd plist management with delegation to bundled CLI; adds shouldReplaceDaemonForDesktop for version-upgrade detection; wires auth token into loadURL calls.
apps/desktop/src/main/service.ts Desktop service management now entirely delegates to the bundled executor CLI binary; removes ~150 lines of duplicated launchd plist generation.
packages/react/src/api/server-connection.tsx Adds desktop-bridge guard to setActiveConnection (blocks non-sidecar connections) and removes initialKey guard from bridge effect; guard silently no-ops without any log.
packages/app/src/web/server-connection-menu.tsx Major hydration refactor: introduces async desktop-bridge fetch inside readStoredProfiles().then(); missing cancellation check after await; hasDesktopServerConnectionBridge has different logic from the same-named helper in server-connection.tsx.
packages/hosts/mcp/src/in-memory-session-store.ts Adds optional principal param to handlePausedRequest/handleApprovalRequest; new ownerAccess helper enforces session ownership with correct 403/404 semantics.
apps/local/src/serve.ts Adds cache-control: no-store to index.html responses to prevent stale HTML caching after upgrades.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Desktop boots] --> B{attachToSupervisedDaemon}
    B -- manifest + pid alive + reachable --> C{shouldReplaceDaemonForDesktop?}
    C -- newer version OR path mismatch --> D[installSupervisedService]
    C -- up to date --> E[attach: supervisedDaemon=true]
    B -- no manifest --> F{supervisedServiceStatus}
    F -- registered + running --> G[waitForSupervisedAttach]
    F -- not running --> H{user confirmed?}
    H -- yes --> D
    H -- no --> I[managed spawn: daemon run --foreground]
    D --> J[planServiceInstall]
    J -- noop --> K[log + return]
    J -- reinstall --> L[bootout then enable then bootstrap]
    J -- takeover-then-install --> M[takeOverActiveLocalServer] --> L
    L --> N[waitForSupervisedAttach]
    N --> E
    G --> E
Loading

Reviews (1): Last reviewed commit: "Fix Windows service uninstall cleanup" | Re-trigger Greptile

Comment on lines 243 to 276

let cancelled = false;
void readStoredProfiles().then((stored) => {
if (cancelled) return;
const storedActive = getActiveExecutorServerProfile(stored);
const next = snapshotWithCurrent(stored, connection, storedActive === null);
persistSnapshot(next);
if (storedActive && storedActive.key !== connection.key) {
setServerConnection(storedActive);
}
setHydrated(true);
void (async () => {
if (cancelled) return;
const desktopBridge = hasDesktopServerConnectionBridge();
const desktopConnection =
(await readDesktopServerConnection()?.then(
(value) => value,
() => null,
)) ?? null;
const storedActive = getActiveExecutorServerProfile(stored);
const current = desktopConnection
? normalizeExecutorServerConnection(desktopConnection)
: connection;
const baseStored = desktopConnection ? withoutLoopbackProfiles(stored) : stored;
const shouldKeepCurrent =
desktopBridge ||
storedActive === null ||
(hasBearerAuth(current) &&
storedActive !== null &&
sameLoopbackServer(current, storedActive));
const next = snapshotWithCurrent(baseStored, current, shouldKeepCurrent);
persistSnapshot(next);
if (desktopConnection) {
setServerConnection(desktopConnection);
} else if (!shouldKeepCurrent && storedActive && storedActive.key !== connection.key) {
setServerConnection(storedActive);
}
setHydrated(true);
})();
});

return () => {

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 Missing cancellation check after async bridge fetch

The if (cancelled) return; guard fires before the await readDesktopServerConnection() call, but not after. If the component unmounts while the bridge fetch is in-flight, cancelled will be true but the code continues past the await and calls setServerConnection, setHydrated(true), and persistSnapshot(next) — the last of which writes to persistent storage with potentially stale data. In React 18 the state-setter calls are silently ignored, but the persistSnapshot write is not gated and can corrupt the stored profile snapshot with a connection computed against the unmounted component's props.

const [connection, setConnection] = React.useState(initialConnection);
const setActiveConnection = React.useCallback((input: ExecutorServerConnectionInput): void => {
const next = normalizeExecutorServerConnection(input);
if (hasDesktopServerConnectionBridge() && next.kind !== "desktop-sidecar") return;

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 Silent no-op may hide connection-switch failures

When hasDesktopServerConnectionBridge() is true and the requested connection is not desktop-sidecar, setActiveConnection returns silently — no console warning, no thrown error, no callback. Any code path that calls setActiveConnection to switch servers (e.g. stored-profile hydration logic in the menu) will appear to succeed but change nothing. Adding a log line makes regressions visible during development without affecting production behavior.

Suggested change
if (hasDesktopServerConnectionBridge() && next.kind !== "desktop-sidecar") return;
if (hasDesktopServerConnectionBridge() && next.kind !== "desktop-sidecar") {
console.warn(
"[executor] setActiveConnection: ignoring non-desktop connection in desktop context",
next.kind,
);
return;
}

Comment on lines 75 to +84
};
};

const hasDesktopServerConnectionBridge = (): boolean =>
Boolean(desktopProfileStorageBridge()) ||
typeof globalThis.window?.executor?.getServerConnection === "function";

const readDesktopServerConnection = (): Promise<ExecutorServerConnectionInput | null> | null => {
const bridge = globalThis.window?.executor;
if (!bridge || typeof bridge.getServerConnection !== "function") return null;

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 hasDesktopServerConnectionBridge duplicated with divergent logic

server-connection.tsx defines the same function name as typeof globalThis.window?.executor?.getServerConnection === "function", while this file's version additionally ORs in Boolean(desktopProfileStorageBridge()) (which checks for executor.getProfiles). If only getProfiles is present (e.g. a partial bridge injection) the menu strips loopback profiles and skips the stored-active switch, but the provider's setActiveConnection guard does not fire — leaving the menu and the connection provider in inconsistent "desktop mode" states. Sharing a single helper (or at minimum aligning the checks) removes the drift risk.

RhysSullivan added a commit that referenced this pull request Jun 15, 2026
#1020 moved the foreground web server behind `executor web --foreground`:
plain `executor web` now opens the installed background service and prints
"Executor is not running" when none exists. The release-bootstrap smoke
test still invoked `executor web --port`, so it never got a served
response and the v1.5.12 publish gate failed at release:check. Pass
--foreground to exercise the same runForegroundSession path the test was
written for.
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