Skip to content

Commit 1c83945

Browse files
committed
always set EXECUTOR_CLIENT so the surface is never guessed
The CLI now stamps EXECUTOR_CLIENT=cli at startup (preserving desktop's own EXECUTOR_CLIENT=desktop), so the in-process apps/local server always sees an explicit surface. apps/local passes the value through, validated, instead of defaulting not-desktop to a guess.
1 parent d9b251b commit 1c83945

3 files changed

Lines changed: 27 additions & 7 deletions

File tree

apps/cli/src/client-env.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Stamp the surface client for this process so the integrations User-Agent is
2+
// always `cli` or `desktop`, never guessed. The desktop app sets
3+
// EXECUTOR_CLIENT=desktop before spawning the CLI as its sidecar/daemon, so
4+
// `??=` preserves that; every other invocation is the CLI itself.
5+
//
6+
// This MUST run before the `@executor-js/local` import graph loads: apps/local
7+
// builds its User-Agent from EXECUTOR_CLIENT at module init (installation.ts),
8+
// so setting the env in main.ts's body would run too late. Imported as an early
9+
// side effect in main.ts, right after native-bindings.
10+
process.env.EXECUTOR_CLIENT ??= "cli";

apps/cli/src/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
// before any import (e.g. `@executor-js/local` → libSQL) eagerly loads them.
33
import "./native-bindings";
44

5+
// Stamp EXECUTOR_CLIENT=cli (unless the desktop app already set `desktop`)
6+
// before `@executor-js/local` builds its integrations User-Agent from it.
7+
import "./client-env";
8+
59
import { randomUUID } from "node:crypto";
610
import { existsSync, realpathSync } from "node:fs";
711
import { dirname, join, resolve } from "node:path";

apps/local/src/installation.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ const resolveChannel = (version: string): InstallationChannel => {
1515
return "stable";
1616
};
1717

18-
// The desktop main process sets `EXECUTOR_CLIENT=desktop` when it spawns the
19-
// sidecar. Otherwise this headless apps/local server was launched by the
20-
// `executor` CLI (`executor mcp`, `web`, `daemon run --foreground`, or the
21-
// installed background service), so it reports as `cli`. Surface is only ever
22-
// `cli` or `desktop`; mirrors the owner.client resolution in apps/cli/src/main.ts.
23-
const resolveClient = (): SurfaceClient =>
24-
process.env.EXECUTOR_CLIENT === "desktop" ? "desktop" : "cli";
18+
// EXECUTOR_CLIENT identifies which product launched this headless apps/local
19+
// server: the desktop app sets `desktop` before spawning it, and the executor
20+
// CLI sets `cli` (apps/cli stamps it at startup, see src/client-env.ts). It is
21+
// therefore always set to a valid surface in a real launch, so we pass it
22+
// through rather than guess. The `cli` floor only covers direct library/test
23+
// imports that boot the server without going through a launcher.
24+
const SURFACE_CLIENTS = ["cli", "desktop"] as const;
25+
const resolveClient = (): SurfaceClient => {
26+
const client = process.env.EXECUTOR_CLIENT;
27+
return (SURFACE_CLIENTS as readonly string[]).includes(client ?? "")
28+
? (client as SurfaceClient)
29+
: "cli";
30+
};
2531

2632
export const CHANNEL: InstallationChannel = resolveChannel(LOCAL_VERSION);
2733
export const VERSION: string = LOCAL_VERSION;

0 commit comments

Comments
 (0)