Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions src/hooks/hermes/wiki-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ async function main(): Promise<void> {
"--ignore-user-config",
], {
stdio: ["ignore", "pipe", "pipe"],
// Suppress the visible console window Windows would otherwise pop for
// a child of this console-less detached worker. No-op on POSIX.
windowsHide: true,
timeout: 120_000,
env: { ...process.env, HIVEMIND_WIKI_WORKER: "1", HIVEMIND_CAPTURE: "false" },
});
Expand Down
13 changes: 9 additions & 4 deletions src/hooks/wiki-worker-spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@ export function buildClaudeInvocation(claudeBin: string, prompt: string): Claude
return {
file: claudeBin,
args: ["-p", ...CLAUDE_FLAGS],
options: { input: prompt, stdio: ["pipe", "pipe", "pipe"], shell: true },
// windowsHide: the wiki worker is a detached, console-less process, so
// without CREATE_NO_WINDOW Windows allocates a visible console window
// (titled after the CLI exe) for the child. No-op on POSIX.
options: { input: prompt, stdio: ["pipe", "pipe", "pipe"], shell: true, windowsHide: true },
};
}
return {
file: claudeBin,
args: ["-p", prompt, ...CLAUDE_FLAGS],
options: { stdio: ["ignore", "pipe", "pipe"] },
options: { stdio: ["ignore", "pipe", "pipe"], windowsHide: true },
};
}

Expand All @@ -63,12 +66,14 @@ export function buildTrailingPromptInvocation(bin: string, flags: string[], prom
return {
file: bin,
args: [...flags],
options: { input: prompt, stdio: ["pipe", "pipe", "pipe"], shell: true },
// windowsHide: see buildClaudeInvocation — suppress the visible console
// window Windows would pop for a child of the console-less worker.
options: { input: prompt, stdio: ["pipe", "pipe", "pipe"], shell: true, windowsHide: true },
};
}
return {
file: bin,
args: [...flags, prompt],
options: { stdio: ["ignore", "pipe", "pipe"] },
options: { stdio: ["ignore", "pipe", "pipe"], windowsHide: true },
};
}
3 changes: 3 additions & 0 deletions src/skillify/gate-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ export function runGate(opts: GateRunOptions): GateRunResult {
try {
const result = runChildProcess(bin, args, {
stdio: ["ignore", "pipe", "pipe"],
// Suppress the visible console window Windows would otherwise pop for
// a child of the console-less detached skillify worker. No-op on POSIX.
windowsHide: true,
timeout: opts.timeoutMs ?? 120_000,
maxBuffer: 8 * 1024 * 1024,
env: { ...inheritedEnv.env, HIVEMIND_WIKI_WORKER: "1", HIVEMIND_CAPTURE: "false" },
Expand Down
13 changes: 13 additions & 0 deletions tests/claude-code/skillify-gate-runner.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { runGate, findAgentBin, type Agent } from "../../src/skillify/gate-runner.js";

describe("gate-runner spawn options", () => {
it("passes windowsHide so the gate CLI never pops a console window on Windows", () => {
// The skillify worker is detached and console-less; without windowsHide
// on the inner CLI spawn, Windows allocates a visible console window.
// Source-level guard because the dispatch tests exec a real /usr/bin/echo
// and can't observe the options object.
const src = readFileSync(join(process.cwd(), "src/skillify/gate-runner.ts"), "utf-8");
expect(src).toContain("windowsHide: true");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
});
});

describe("findAgentBin", () => {
it("returns a path for each known agent (PATH lookup or fallback)", () => {
for (const agent of ["claude_code", "codex", "cursor", "hermes", "pi"] as Agent[]) {
Expand Down
23 changes: 23 additions & 0 deletions tests/claude-code/wiki-worker-windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,26 @@ describe("buildTrailingPromptInvocation (codex / cursor / pi)", () => {
expect(inv.args).toEqual([...FLAGS, "PROMPT-TEXT"]);
});
});

describe("windowsHide — no visible console window for the summarizer CLI", () => {
// The wiki worker is spawned detached and console-less (spawn-detached.ts
// sets windowsHide on the worker itself). Without CREATE_NO_WINDOW on the
// INNER spawn too, Windows allocates a fresh visible console window titled
// after the CLI exe (users reported a bare "claude.exe" window popping up).
it("buildClaudeInvocation sets windowsHide on every branch", () => {
setPlatform("win32");
expect(buildClaudeInvocation("C:\\npm\\claude.cmd", "P").options.windowsHide).toBe(true);
expect(buildClaudeInvocation("C:\\pf\\claude.exe", "P").options.windowsHide).toBe(true);
setPlatform("linux");
expect(buildClaudeInvocation("/usr/local/bin/claude", "P").options.windowsHide).toBe(true);
});

it("buildTrailingPromptInvocation sets windowsHide on every branch", () => {
const FLAGS = ["exec"];
setPlatform("win32");
expect(buildTrailingPromptInvocation("C:\\npm\\codex.cmd", FLAGS, "P").options.windowsHide).toBe(true);
expect(buildTrailingPromptInvocation("C:\\pf\\codex.exe", FLAGS, "P").options.windowsHide).toBe(true);
setPlatform("linux");
expect(buildTrailingPromptInvocation("/usr/local/bin/codex", FLAGS, "P").options.windowsHide).toBe(true);
});
});
6 changes: 6 additions & 0 deletions tests/hermes/hermes-wiki-worker-source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ describe("hermes wiki-worker source", () => {
expect(WORKER_SRC).not.toMatch(/"--dangerously-bypass-approvals-and-sandbox"/);
});

it("hermes spawn passes windowsHide (no visible console window on Windows)", () => {
// hermes is the only worker that calls execFileSync directly instead of
// going through the wiki-worker-spawn builders, so it needs its own guard.
expect(WORKER_SRC).toContain("windowsHide: true");
});

it("config carries hermesBin + hermesProvider + hermesModel (not codexBin)", () => {
expect(WORKER_SRC).toContain("hermesBin: string");
expect(WORKER_SRC).toContain("hermesProvider: string");
Expand Down
Loading