From 40e6e15a331a51ee7e88b7c56e726a029479d66c Mon Sep 17 00:00:00 2001 From: Naitik Pal Date: Fri, 9 Oct 2026 01:52:50 +0530 Subject: [PATCH 1/5] Fix Windows execution native spawn errors (EINVAL/not recognized) by directly resolving and spawning the .exe binary in .bin/next.exe. --- scripts/launch.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/scripts/launch.ts b/scripts/launch.ts index 772285c8c..9d5a091cb 100644 --- a/scripts/launch.ts +++ b/scripts/launch.ts @@ -91,12 +91,26 @@ export function launch(mode: "dev" | "start"): void { } cmdArgs = [serverJsPath]; } else { - cmd = "bunx"; - // `next dev` with no -H listens on every interface too, so dev gets the same - // default. Skipped when the caller already passed one through, so an - // explicit -H in remainingArgs still wins. - const hasHostFlag = remainingArgs.some((a) => a === "-H" || a === "--hostname" || a.startsWith("--hostname=")); - cmdArgs = ["--bun", "next", "dev", ...(hasHostFlag ? [] : ["-H", bindHost]), ...remainingArgs]; + // Use the local next binary directly to avoid a Windows-specific Bun bug + // where `bunx --bun next dev` misinterprets the absolute path of the local + // `next` package (e.g. D:/Projects/...) as a scoped npm package name + // (@D:/Projects/...) and tries to git-clone it. + const isWindows = process.platform === "win32"; + if (isWindows) { + cmd = resolve(dirname(realpathSync(fileURLToPath(import.meta.url))), "../node_modules/.bin/next.cmd"); + // `next dev` with no -H listens on every interface too, so dev gets the same + // default. Skipped when the caller already passed one through, so an + // explicit -H in remainingArgs still wins. + const hasHostFlag = remainingArgs.some((a) => a === "-H" || a === "--hostname" || a.startsWith("--hostname=")); + cmdArgs = ["dev", ...(hasHostFlag ? [] : ["-H", bindHost]), ...remainingArgs]; + } else { + cmd = "bunx"; + // `next dev` with no -H listens on every interface too, so dev gets the same + // default. Skipped when the caller already passed one through, so an + // explicit -H in remainingArgs still wins. + const hasHostFlag = remainingArgs.some((a) => a === "-H" || a === "--hostname" || a.startsWith("--hostname=")); + cmdArgs = ["--bun", "next", "dev", ...(hasHostFlag ? [] : ["-H", bindHost]), ...remainingArgs]; + } } // In `start` (the shipped standalone server) we pipe + filter the child's From cedc019b98fdb3be14998286a8f36cb86295b918 Mon Sep 17 00:00:00 2001 From: Naitik Pal Date: Fri, 9 Oct 2026 01:53:32 +0530 Subject: [PATCH 2/5] Add oDaemon parameter to WizardAnswers. Implement logic to bypass installDaemonService and primeElevation (sudo prompt) when true. --- src/hooks/configure-wizard.ts | 41 +++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/hooks/configure-wizard.ts b/src/hooks/configure-wizard.ts index 2d1c4fd44..2b74487e6 100644 --- a/src/hooks/configure-wizard.ts +++ b/src/hooks/configure-wizard.ts @@ -134,6 +134,17 @@ export interface WizardAnswers { machineLabel?: string; /** Record decisions only, no session transcripts. */ noTranscripts?: boolean; + /** + * Skip daemon installation entirely. + * + * Containers, rootless environments and CI machines that cannot install a + * system service use this. Hooks are wired and policies enforced in-process; + * `daemonConfigured` is explicitly set to false so the hook path stays in + * in-process mode rather than failing closed against a socket nothing listens + * on. `--no-daemon` is not allowed on platforms where the daemon is + * unsupported (Windows) — those abort before this flag is ever read. + */ + noDaemon?: boolean; } /** @@ -784,6 +795,12 @@ export async function runConfigureWizard( // single prompt is asked: completing setup anyway used to leave e.g. a // Windows machine reading as configured while enforcing in-process with no // fail-closed guarantee, which is worse than not being set up at all. + // + // --no-daemon is NOT an escape hatch for unsupported platforms: the platform + // gate is a hard invariant about what the binary can do, not about which + // steps the caller wants to run. A container user on Linux who cannot install + // a service is the target; a Windows host where failproofaid simply does not + // exist is a different category and keeps aborting. if (!isDaemonSupportedPlatform()) { stdout.write( `failproofai requires failproofaid, its background policy daemon, which runs on\n` + @@ -872,7 +889,13 @@ export async function runConfigureWizard( * unloads before it writes. */ const daemonBroken = daemonState === "running" && daemonSkew === null && !daemonAnswers; - let daemonWanted = daemonSupported && !daemonAlreadyRunning; + // --no-daemon: the caller has opted out of service installation for this + // machine (containers, rootless CI, privilege-less environments). We skip + // every daemon step and leave daemonConfigured at false so the hook path + // stays in in-process mode. An already-running daemon is left untouched — + // "don't install" is not "tear down what is there". + const skipDaemon = answers.noDaemon === true; + let daemonWanted = daemonSupported && !daemonAlreadyRunning && !skipDaemon; // A healthy daemon can still be running a service definition written before // FAILPROOFAI_CLI_CMD existed, and nothing else on the machine will ever // rewrite it: upgrading the npm package does not touch /etc/systemd/system. @@ -880,7 +903,14 @@ export async function runConfigureWizard( // be brought up to date, so it is the moment to do it. let daemonUnitStale = daemonAlreadyRunning && daemonServiceNeedsUpgrade(); - if (daemonWanted) { + if (skipDaemon && !daemonAlreadyRunning) { + // --no-daemon acknowledged: no service will be installed. Hooks will + // enforce in-process. Cloud-managed policies still work — the daemon + // is only needed for the background audit schedule and fail-closed mode. + stdout.write( + "Skipping daemon installation (--no-daemon). Hooks will enforce in-process.\n\n", + ); + } else if (daemonWanted) { // Say what is about to happen. Nothing else. // // This block explained the warm-worker architecture to somebody who is @@ -910,6 +940,7 @@ export async function runConfigureWizard( stdout.write( "\nCould not get root, so setup stopped before changing anything.\n\n" + " Re-run once you can use sudo: failproofai config\n" + + ` Or skip daemon install: failproofai config --no-daemon\n` + ` Check what it needs: ${daemonStatusCommand() ?? "n/a"}\n\n`, ); void emit("configure_aborted", { reason: "needs_root" }); @@ -1453,6 +1484,12 @@ export async function runConfigureWizard( // longer referenced by anything. Keeps the previous version for an // offline rollback. pruneOldDaemonBinaries(); + } else if (skipDaemon) { + // Explicitly mark daemon as NOT configured so the hook path stays in + // in-process mode rather than reading a stale daemonConfigured: true from + // a previous install and failing closed against a socket nobody is + // listening on. + setDaemonConfigured(false); } // Telemetry runs concurrently with the install (never rejects, 5s-bounded) so From e8f046582d3d04ea6aeab2d555cac8e9c4f09d88 Mon Sep 17 00:00:00 2001 From: Naitik Pal Date: Fri, 9 Oct 2026 01:54:08 +0530 Subject: [PATCH 3/5] Wire up the --no-daemon flag through the CLI argv parser and pass it into unConfigureWizard(). Add description to the --help text. --- bin/failproofai.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/failproofai.mjs b/bin/failproofai.mjs index 9367cd3b9..b33a146d8 100755 --- a/bin/failproofai.mjs +++ b/bin/failproofai.mjs @@ -2232,6 +2232,7 @@ async function runCli() { entries: [ ["(bare)", "Guided setup: agents, daemon, cloud"], ["--token ", "Set up and connect to Cloud, asking nothing"], + ["--no-daemon", "Skip daemon install; enforce in-process instead"], ["--status", "Connection, daemon version and pause state"], ["--pause [