mirror of
https://github.com/cline/cline.git
synced 2026-09-19 02:05:44 +08:00
* feat(shared): add disableCurrentDirectoryExecutableSearch for Windows
libuv resolves a bare program name on Windows by searching the child's
working directory before PATH, gated on the spawning process having
NoDefaultCurrentDirectoryInExePath defined. Cline spawns rg, git and
powershell with the user's workspace as cwd, so a repo shipping an rg.exe
would get it executed at index time. Expose a one-call helper that sets
Microsoft's documented opt-out, plus a Windows-only test that plants a
zero-byte cmd.exe and asserts the real one still runs.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: stop Windows resolving bare program names through the workspace cwd
Call disableCurrentDirectoryExecutableSearch() at startup in every
process that hosts Cline core: the CLI (and the hub daemon it boots), the
desktop sidecar, the VS Code extension host, and the JetBrains cline-core
process. One environment variable covers every spawn site (file indexer,
search, simple-git, shell executor, hooks, MCP, taskkill) and is inherited
by children, which cmd.exe, libuv and Bun 1.4+ honor as well.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* fix: hand user-facing children the inherited NoDefaultCurrentDirectoryInExePath state
libuv reads the opt-out from the spawning process, so Cline's own protection
does not depend on children carrying it. But cmd.exe, Go, Bun 1.4 and
libuv-based children honor it as well, so letting them inherit Cline's
setting would silently change how a user's own bare program names resolve
(npm scripts running a cwd-local .bat through cmd.exe, MCP servers named
relative to cwd, hooks that spawn helpers).
disableCurrentDirectoryExecutableSearch() now latches the value the process
inherited, and withInheritedExecutableSearch() restores that state on the
child env at the spawn sites that run user-authored programs: the shell
executor, MCP stdio servers, hook subprocesses, the plugin subprocess
sandbox, and the VS Code host's HookProcess.
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
* Revert "fix: hand user-facing children the inherited NoDefaultCurrentDirectoryInExePath state"
This reverts commit b643517257.
---------
Co-authored-by: Saoud Rizwan <saoudrizwan@users.noreply.github.com>
116 lines
3.7 KiB
TypeScript
Executable File
116 lines
3.7 KiB
TypeScript
Executable File
#!/usr/bin/env bun
|
|
|
|
import { isMainThread } from "node:worker_threads";
|
|
import {
|
|
claimHubDaemonProcess,
|
|
claimSupervisedConnectorProcess,
|
|
disableCurrentDirectoryExecutableSearch,
|
|
disposeAll,
|
|
initVcr,
|
|
setConnectorCliLaunchSpec,
|
|
} from "@cline/shared";
|
|
import { logCliProcessError } from "./logging/errors";
|
|
import {
|
|
abortActiveRuntime,
|
|
cleanupActiveRuntime,
|
|
isAbortInProgress,
|
|
} from "./runtime/active-runtime";
|
|
import { resolveCliLaunchSpec } from "./utils/internal-launch";
|
|
import { writeErr } from "./utils/output";
|
|
|
|
// Initialize VCR before any HTTP requests are made.
|
|
// Set CLINE_VCR=record|playback and CLINE_VCR_CASSETTE=<path> to enable.
|
|
initVcr(process.env.CLINE_VCR);
|
|
|
|
// Before any personality below can spawn a child with the workspace as cwd.
|
|
disableCurrentDirectoryExecutableSearch();
|
|
|
|
if (!isMainThread) {
|
|
// Worker imports of the bundled CLI entrypoint should not start the CLI.
|
|
} else if (claimHubDaemonProcess()) {
|
|
// Claim rather than read: the sentinel is consumed here so the processes a
|
|
// daemon-hosted session spawns do not inherit it and try to become daemons.
|
|
// The hub daemon owns its process-level abort handling. Installing the CLI's
|
|
// fatal rejection handler first would make expected abort rejections exit it.
|
|
void import("@cline/core/hub/daemon-entry");
|
|
} else {
|
|
// Same reasoning as the daemon sentinel above: consume the supervised-connector
|
|
// marker so the processes an agent session spawns cannot inherit it and mistake
|
|
// themselves for the connector the hub is tracking.
|
|
claimSupervisedConnectorProcess();
|
|
|
|
const cliLaunchSpec = resolveCliLaunchSpec({ debugRole: "connector" });
|
|
if (cliLaunchSpec) {
|
|
setConnectorCliLaunchSpec({
|
|
launcher: cliLaunchSpec.launcher,
|
|
connectArgsPrefix: [...cliLaunchSpec.childArgsPrefix, "connect"],
|
|
cwd: process.cwd(),
|
|
});
|
|
}
|
|
|
|
let shuttingDown = false;
|
|
let handlingFatalProcessError = false;
|
|
const forwardSignalToRuntime = () => {
|
|
if (shuttingDown) {
|
|
process.exit(1);
|
|
}
|
|
shuttingDown = true;
|
|
abortActiveRuntime();
|
|
};
|
|
process.on("SIGINT", forwardSignalToRuntime);
|
|
process.on("SIGTERM", forwardSignalToRuntime);
|
|
const handleFatalProcessError = (kind: string, error: unknown) => {
|
|
if (handlingFatalProcessError) {
|
|
process.exit(1);
|
|
}
|
|
handlingFatalProcessError = true;
|
|
logCliProcessError(kind, error);
|
|
writeErr(
|
|
error instanceof Error ? (error.stack ?? error.message) : String(error),
|
|
);
|
|
cleanupActiveRuntime();
|
|
abortActiveRuntime();
|
|
void disposeAll().finally(() => {
|
|
process.exit(1);
|
|
});
|
|
};
|
|
process.on("uncaughtException", (error) => {
|
|
handleFatalProcessError("uncaughtException", error);
|
|
});
|
|
process.on("unhandledRejection", (reason, promise) => {
|
|
if (isAbortInProgress()) {
|
|
// Mark the promise as handled so OpenTUI's error overlay
|
|
// does not surface expected abort-related rejections.
|
|
promise.catch(() => {});
|
|
return;
|
|
}
|
|
handleFatalProcessError("unhandledRejection", reason);
|
|
});
|
|
|
|
void (async () => {
|
|
let exitCode = 0;
|
|
try {
|
|
const { runCli } = await import("./main");
|
|
await runCli();
|
|
} catch (err) {
|
|
logCliProcessError("runCli", err);
|
|
writeErr(err instanceof Error ? err.message : String(err));
|
|
cleanupActiveRuntime();
|
|
abortActiveRuntime();
|
|
exitCode = 1;
|
|
} finally {
|
|
await disposeAll();
|
|
}
|
|
// The explicit process.exit below means beforeExit never fires, so a
|
|
// startup-recorded auto-update must be applied here, after all runtime
|
|
// teardown. It spawns detached and only when no other CLI is attached.
|
|
try {
|
|
const { applyDeferredUpdate } = await import("./commands/update");
|
|
await applyDeferredUpdate();
|
|
} catch {
|
|
// Best-effort; never block exit on the updater.
|
|
}
|
|
process.exit(exitCode || (process.exitCode as number) || 0);
|
|
})();
|
|
}
|