From e2f894ddb424b526b1af0fff0efdcc63e79171a9 Mon Sep 17 00:00:00 2001 From: Andy Haigh <124785694+Andy-Haigh@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:42:33 +0200 Subject: [PATCH] =?UTF-8?q?fix(slop-diff):=20redirect=20slop-scan=20stdout?= =?UTF-8?q?=20to=20a=20file=20=E2=80=94=20piped=20output=20truncates=20at?= =?UTF-8?q?=208KB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit slop-scan@0.3.0 emits its JSON report via async stdout writes and then calls process.exit(), so when stdout is a PIPE (spawnSync capture — this wrapper), the report is truncated at the first ~8KB chunk. A full-repo report is multi-MB, so both scans always failed JSON.parse and the /review and /ship integration silently reported 'slop-scan returned invalid JSON' (or, for the base scan, diffed against an empty baseline). File writes are synchronous, so redirecting the child's stdout to a temp file yields the complete report regardless of size. Repro: npx slop-scan scan . --json | wc -c → 8192 on any large repo, while a shell file-redirect of the same command yields the full report. Both scan sites now go through one runSlopScan() helper (fd-backed stdout, temp file cleaned up in finally). Behavior otherwise unchanged. Co-Authored-By: Claude Fable 5 --- scripts/slop-diff.ts | 58 ++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/scripts/slop-diff.ts b/scripts/slop-diff.ts index b2a5abd17d..c44524ce60 100644 --- a/scripts/slop-diff.ts +++ b/scripts/slop-diff.ts @@ -31,19 +31,49 @@ if (changedFiles.size === 0) { process.exit(0); } + +/** + * Run `npx slop-scan scan --json` with stdout redirected to a temp + * FILE and return the report text. + * + * Why not capture the pipe: slop-scan@0.3.0 writes its report with async + * stdout writes and then calls process.exit(), so on a PIPED stdout the + * output is truncated at the first ~8KB chunk (a full-repo report is + * multi-MB). File writes are synchronous, so redirecting to a file yields + * the complete report regardless of size. Returns null when slop-scan is + * unavailable or produced no output. + */ +function runSlopScan(target: string): string | null { + const outFile = path.join( + os.tmpdir(), + `slop-scan-out-${process.pid}-${Date.now()}.json`, + ); + const outFd = fs.openSync(outFile, "w"); + try { + spawnSync("npx", ["slop-scan", "scan", target, "--json"], { + stdio: ["ignore", outFd, "pipe"], + timeout: 120000, + shell: process.platform === "win32", + }); + fs.closeSync(outFd); + const raw = fs.readFileSync(outFile, "utf-8"); + return raw.length > 0 ? raw : null; + } catch { + return null; + } finally { + fs.rmSync(outFile, { force: true }); + } +} + // 2. Run slop-scan on HEAD -const scanHead = spawnSync("npx", ["slop-scan", "scan", ".", "--json"], { - encoding: "utf-8", - timeout: 120000, - shell: process.platform === "win32", -}); -if (!scanHead.stdout) { +const scanHeadOut = runSlopScan("."); +if (!scanHeadOut) { console.log("slop-scan not available. Install: npm i -g slop-scan"); process.exit(0); } let headReport: any; try { - headReport = JSON.parse(scanHead.stdout); + headReport = JSON.parse(scanHeadOut); } catch { console.log("slop-scan returned invalid JSON."); process.exit(0); @@ -86,19 +116,11 @@ if (mergeBase) { } catch {} } - const scanBase = spawnSync( - "npx", - ["slop-scan", "scan", tmpWorktree, "--json"], - { - encoding: "utf-8", - timeout: 120000, - shell: process.platform === "win32", - }, - ); + const scanBaseOut = runSlopScan(tmpWorktree); - if (scanBase.stdout) { + if (scanBaseOut) { try { - const baseReport = JSON.parse(scanBase.stdout); + const baseReport = JSON.parse(scanBaseOut); for (const f of baseReport.findings) { // Remap worktree paths back to repo-relative const realPath = f.path.replace(tmpWorktree + "/", "");