feat(FN-3815): quiet stderr logging for clean-exit MCP processes in pi-clau

Adds a changeset for FN-3815. The core change reclassifies clean-exit stderr output in the pi-claude-cli provider to reduce noisy logging, with corresponding test coverage distinguishing between clean and non-zero exit stderr handling.

Fusion-Task-Id: FN-3815
This commit is contained in:
Fusion
2026-05-11 01:18:33 -07:00
committed by gsxdsm
parent a59c84753d
commit a51e7791a5
3 changed files with 45 additions and 18 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
Quiet benign Claude Code CLI stderr on clean shutdown by routing it to debug-only logs in `pi-claude-cli`.
This prevents MCP loading/initialization lines from surfacing as warning/error-level entries in the TUI Logs tab when Claude exits cleanly, while preserving warning/error surfacing for non-zero Claude CLI exits and authentication-related failures.

View File

@@ -1188,28 +1188,42 @@ describe("streamViaCli", { timeout: 90_000 }, () => {
expect(doneEvent.message.content).toBeDefined(); expect(doneEvent.message.content).toBeDefined();
}); });
it("logs stderr at warn level on close even with exit code 0", async () => { it.each([
const model = mockModels[0] as any; { code: 0, shouldWarn: false },
const context = { { code: 42, shouldWarn: true },
messages: [{ role: "user", content: "Hello" }], ])(
}; "FN-3815: routes close stderr logging by exit code=$code",
async ({ code, shouldWarn }) => {
const model = mockModels[0] as any;
const context = {
messages: [{ role: "user", content: "Hello" }],
};
const warnSpy = vi.spyOn(console, "warn"); const warnSpy = vi.spyOn(console, "warn");
streamViaCli(model, context); streamViaCli(model, context);
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
const proc = (spawn as any).mock.results[0].value; const proc = (spawn as any).mock.results[0].value;
proc.stderr.emit("data", Buffer.from("minor warning from cli")); proc.stderr.emit("data", Buffer.from("minor warning from cli"));
proc.emit("close", 0, null); proc.emit("close", code, null);
proc.stdout.end(); proc.stdout.end();
await vi.advanceTimersByTimeAsync(100); await vi.advanceTimersByTimeAsync(100);
expect(warnSpy).toHaveBeenCalledWith( const stderrWarnCalls = warnSpy.mock.calls.filter(([message]) =>
expect.stringContaining("minor warning from cli"), typeof message === "string" &&
); message.includes("[pi-claude-cli] Claude CLI stderr on close:"),
}); );
if (shouldWarn) {
expect(stderrWarnCalls).toHaveLength(1);
expect(stderrWarnCalls[0]?.[0]).toContain("minor warning from cli");
} else {
expect(stderrWarnCalls).toHaveLength(0);
}
},
);
it("warns when subprocess closes successfully with no content events", async () => { it("warns when subprocess closes successfully with no content events", async () => {
const model = mockModels[0] as any; const model = mockModels[0] as any;

View File

@@ -252,7 +252,13 @@ export function streamViaCli(
if (broken) return; // Break-early kill, expected if (broken) return; // Break-early kill, expected
const stderr = getStderr().trim(); const stderr = getStderr().trim();
if (stderr) { if (stderr) {
console.warn(`[pi-claude-cli] Claude CLI stderr on close: ${stderr}`); if (code === 0 || code === null) {
// FN-3815: Claude CLI writes benign MCP bring-up diagnostics to stderr
// on clean/abort shutdown; keep these debug-only to avoid false TUI warnings.
debugLog(`Claude CLI stderr on close (clean exit): ${stderr}`);
} else {
console.warn(`[pi-claude-cli] Claude CLI stderr on close: ${stderr}`);
}
} }
if (code !== 0 && code !== null) { if (code !== 0 && code !== null) {
const message = stderr const message = stderr