diff --git a/scripts/lib/plain-gh.mjs b/scripts/lib/plain-gh.mjs index da3773668e7..23e65db9e46 100644 --- a/scripts/lib/plain-gh.mjs +++ b/scripts/lib/plain-gh.mjs @@ -2,6 +2,8 @@ import { execFileSync, spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; +export const PLAIN_GH_MAX_BUFFER_BYTES = 32 * 1024 * 1024; + function isExecutable(filePath) { try { fs.accessSync(filePath, fs.constants.X_OK); @@ -71,6 +73,7 @@ export function execPlainGh(args, options = {}) { return execFileSync(ghBin, args, { ...options, env, + maxBuffer: options.maxBuffer ?? PLAIN_GH_MAX_BUFFER_BYTES, }); } @@ -80,5 +83,6 @@ export function spawnPlainGh(args, options = {}) { return spawnSync(ghBin, args, { ...options, env, + maxBuffer: options.maxBuffer ?? PLAIN_GH_MAX_BUFFER_BYTES, }); } diff --git a/test/scripts/plain-gh.test.ts b/test/scripts/plain-gh.test.ts index 7874380dc48..e7520342c10 100644 --- a/test/scripts/plain-gh.test.ts +++ b/test/scripts/plain-gh.test.ts @@ -4,7 +4,7 @@ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { plainGhEnv, resolvePlainGhBin } from "../../scripts/lib/plain-gh.mjs"; +import { execPlainGh, plainGhEnv, resolvePlainGhBin } from "../../scripts/lib/plain-gh.mjs"; const tempDirs: string[] = []; @@ -36,6 +36,21 @@ printf 'COLORTERM_SET=%s\\n' "\${COLORTERM+x}" return ghPath; } +function makeLargeFakeGh(): string { + const dir = mkdtempSync(path.join(tmpdir(), "plain-gh-large-")); + tempDirs.push(dir); + const ghPath = path.join(dir, "gh"); + writeFileSync( + ghPath, + `#!/usr/bin/env node +const bytes = Number(process.env.PLAIN_GH_FAKE_BYTES ?? "0"); +process.stdout.write("x".repeat(bytes)); +`, + ); + chmodSync(ghPath, 0o755); + return ghPath; +} + describe("plain gh helpers", () => { it("prefers OPENCLAW_GH_BIN over PATH shims", () => { const ghPath = makeFakeGh(); @@ -99,6 +114,22 @@ describe("plain gh helpers", () => { expect(readFileSync(outputPath, "utf8")).toContain("COLORTERM_SET="); }); + it("captures large gh payloads by default", () => { + const ghPath = makeLargeFakeGh(); + const bytes = 2 * 1024 * 1024; + + const output = execPlainGh(["api", "large"], { + encoding: "utf8", + env: { + ...process.env, + OPENCLAW_GH_BIN: ghPath, + PLAIN_GH_FAKE_BYTES: String(bytes), + }, + }); + + expect(output).toHaveLength(bytes); + }); + it("keeps the shell resolver on external gh binaries", () => { const helper = readFileSync("scripts/lib/plain-gh.sh", "utf8");