Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cli/ensure-desktop-cold-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,12 @@ function consentedDesktopBundle(home: string, installId: string, run: Execute):
const plistPath = realpathSync(join(home, "Library", "LaunchAgents", `${DESKTOP_PLIST_LABEL}.plist`));
const registration = JSON.parse(
run("/usr/bin/plutil", ["-convert", "json", "-o", "-", plistPath], 3_000),
) as { Label?: unknown; ProgramArguments?: unknown; Program?: unknown };
) as { Label?: unknown; RunAtLoad?: unknown; ProgramArguments?: unknown; Program?: unknown };
const args = registration.ProgramArguments;
// The shapes auto-launch writes: [program] or [program, "--autostart"]; see the migration
// rules in desktop/src-tauri/src/login_registration.rs, which classify anything else Leave.
// This is a macOS plist path, regardless of the host running an injected launch probe.
if (registration.Label !== DESKTOP_PLIST_LABEL || !Array.isArray(args)
if (registration.Label !== DESKTOP_PLIST_LABEL || registration.RunAtLoad !== true || !Array.isArray(args)
|| (args.length !== 1 && args.length !== 2) || typeof args[0] !== "string" || !posix.isAbsolute(args[0])
|| (args.length === 2 && args[1] !== "--autostart")
|| (registration.Program !== undefined && registration.Program !== args[0])) return null;
Expand Down
2 changes: 1 addition & 1 deletion structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ Codex compaction uses a request-local model override for the configured triggers
state, beside the install provenance. The claim carries an `owner` (`cli` or `desktop`), an
opaque `installId` naming the owning installation rather than the user or the machine, and a
`consentGeneration`. An absent claim means the CLI install that registered the service owns
the runtime, which is what every record written before the field existed says. `src/service/desktop-startup.ts` separates the durable macOS, Linux and Windows desktop claim from startup viability. Windows Desktop identity is read from `%APPDATA%\com.opencodex.desktop\install-id` and checked against live parentage; Windows Start at Login is unverified and Desktop never receives startup protection credit there. Independent [live Desktop supervision](desktop-shell.md#runtime-ownership-from-the-apps-side) can also grant startup protection without a claim when that app's login registration is verified; it never becomes ownership. On Linux, auto-launch 0.5.0 writes the login entry under HOME/.config/autostart, so a relocated XDG_CONFIG_HOME (outside that search path) is uncredited. Conditional entries and ambiguous Exec syntax are uncredited; the resolved desktop basename must match and its resolved ocx stays in the same directory. Both complete evidence reads must agree on ownership, install-id, entry contents, executable targets, PID and parent relationship; changes fail closed. AppImage registrations are uncredited because they name the outer image rather than the mounted executable. `src/server/startup-health-cache.ts` launches source and compiled probes with `selfLaunchArgv`, revokes stale protection, and preserves desktop recovery guidance. An enabled, identity-matching macOS LaunchAgent registration remains viable when `launchctl print` specifically proves the job is absent in the current session. `src/service/desktop-startup.ts` reports `registered-not-loaded` for next login; wrong plist paths/arguments/label, disabled override, unknown failures and supervisor races still refuse credit. `src/codex/autostart-health.ts` distinguishes this protected registration in restart safety. `src/service/linux-linger.ts` uses bounded `loginctl show-user` to report already-enabled linger, advise activation only when disabled, or explicitly report unavailable diagnosis. `src/cli/ensure-desktop-cold-start.ts` launches a claimed Desktop only on macOS. Its LaunchAgent executable path must be POSIX-absolute, including when the launch probe is exercised on another host; Windows drive paths do not satisfy that plist contract. Launches on Linux and Windows refuse without invoking a command.
the runtime, which is what every record written before the field existed says. `src/service/desktop-startup.ts` separates the durable macOS, Linux and Windows desktop claim from startup viability. Windows Desktop identity is read from `%APPDATA%\com.opencodex.desktop\install-id` and checked against live parentage; Windows Start at Login is unverified and Desktop never receives startup protection credit there. Independent [live Desktop supervision](desktop-shell.md#runtime-ownership-from-the-apps-side) can also grant startup protection without a claim when that app's login registration is verified; it never becomes ownership. On Linux, auto-launch 0.5.0 writes the login entry under HOME/.config/autostart, so a relocated XDG_CONFIG_HOME (outside that search path) is uncredited. Conditional entries and ambiguous Exec syntax are uncredited; the resolved desktop basename must match and its resolved ocx stays in the same directory. Both complete evidence reads must agree on ownership, install-id, entry contents, executable targets, PID and parent relationship; changes fail closed. AppImage registrations are uncredited because they name the outer image rather than the mounted executable. `src/server/startup-health-cache.ts` launches source and compiled probes with `selfLaunchArgv`, revokes stale protection, and preserves desktop recovery guidance. An enabled, identity-matching macOS LaunchAgent registration remains viable when `launchctl print` specifically proves the job is absent in the current session. `src/service/desktop-startup.ts` reports `registered-not-loaded` for next login; wrong plist paths/arguments/label, disabled override, unknown failures and supervisor races still refuse credit. `src/codex/autostart-health.ts` distinguishes this protected registration in restart safety. `src/service/linux-linger.ts` uses bounded `loginctl show-user` to report already-enabled linger, advise activation only when disabled, or explicitly report unavailable diagnosis. `src/cli/ensure-desktop-cold-start.ts` launches a claimed Desktop only on macOS. Its LaunchAgent requires `RunAtLoad: true` and a POSIX-absolute executable path, including when the launch probe is exercised on another host; Windows drive paths do not satisfy that plist contract. Both `[program, "--autostart"]` and the legacy `[program]` arguments remain accepted. When this caller attempts the relaunch, a false or missing `RunAtLoad` returns manual-open guidance without waiting for readiness or falling back to a CLI proxy; an already-running Desktop still follows the wait path. Launches on Linux and Windows refuse without invoking a command.

Every write goes through `swapServiceInstallState`. Guarded tests use only their selected home; see [test state isolation](ops/test-sandbox-cleanup.md). Production retains both paths. With a custom home, the default-home
record is the authority every writer can derive and the active-home record is a compatibility
Expand Down
24 changes: 22 additions & 2 deletions tests/cli/cli-ensure-desktop-cold-start.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ describe("Desktop process detection by executable identity", () => {
function fakeDesktopHome(options: {
installId?: string; args?: unknown; bareArgs?: boolean; label?: string; identifier?: string;
plist?: "present" | "absent"; executable?: "present" | "absent" | "directory"; bundleName?: string;
bundleExecutable?: unknown; program?: unknown; relativeProgram?: boolean;
bundleExecutable?: unknown; program?: unknown; relativeProgram?: boolean; runAtLoad?: unknown;
} = {}) {
// Keep the bundle on cwd's drive: Windows relative() returns an absolute path across drives.
const fixtureRoot = join(process.cwd(), ".tmp");
Expand All @@ -190,7 +190,7 @@ describe("Desktop process detection by executable identity", () => {
// A root-relative POSIX spelling also reaches this same-drive fixture on Windows.
const absoluteProgram = process.platform === "win32" ? executable.slice(2).replaceAll("\\", "/") : executable;
const program = options.relativeProgram ? relative(process.cwd(), executable) : absoluteProgram;
const registration = { Label: options.label ?? "OpenCodex", RunAtLoad: true,
const registration = { Label: options.label ?? "OpenCodex", RunAtLoad: "runAtLoad" in options ? options.runAtLoad : true,
ProgramArguments: options.bareArgs ? [program] : (options.args ?? [program, "--autostart"]),
...("program" in options ? { Program: options.program } : {}) };
if (options.plist !== "absent") writeFileSync(join(home, "Library", "LaunchAgents", "OpenCodex.plist"), JSON.stringify(registration));
Expand Down Expand Up @@ -223,6 +223,26 @@ describe("Desktop process detection by executable identity", () => {
} finally { removeTreeWithRetry(fixture.home); }
});

test("a false or missing RunAtLoad refuses launch without waiting or falling back to a CLI proxy", async () => {
for (const runAtLoad of [false, undefined]) {
const fixture = fakeDesktopHome({ installId: "install-a", runAtLoad });
try {
expect(launch(fixture)).toBe(false);
const calls = fresh();
const dependencies = deps({ ownership: "desktop", process: "absent" }, calls);
dependencies.launchDesktop = () => launch(fixture);
const result = await runDesktopColdStart(dependencies);
let cliFallbacks = 0;
if (result.kind === "cli") cliFallbacks++;
expect(result.kind === "failed" && result.message).toContain("Open OpenCodex manually");
expect(calls.waits).toEqual([]);
expect(calls.released).toBe(1);
expect(cliFallbacks).toBe(0);
expect(fixture.seen.filter(call => call[0] === "/usr/bin/open")).toHaveLength(0);
} finally { removeTreeWithRetry(fixture.home); }
}
});

test("a non-app directory refuses the launch without reading its Info.plist or waiting for a proxy", async () => {
for (const bundleName of ["OpenCodex", "OpenCodex.app.backup"]) {
const fixture = fakeDesktopHome({ installId: "install-a", bundleName });
Expand Down
Loading