perf(cli): write compact JSON when output is not a terminal (#1413)

`--json` / `--format json` always indented its streamed result. Agents and
scripts read REA through pipes and redirects, where indentation carries no
information: on real application results it added 16-45% model tokens
(o200k proxy) and 25-73% bytes. The skill's CLI fallback uses `--json`.

Indent only when stdout is a TTY, as `gh --json` does; piped or redirected
JSON is compact. The JSON value, `jsonl`, `--filter-output`,
`--full-output` and the token-window paths are unchanged. `jq .` restores
indentation for files.

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Kaoru0822
2026-10-09 20:29:48 -07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent de8fc67195
commit ca3131b36c
5 changed files with 53 additions and 19 deletions
+4 -1
View File
@@ -179,7 +179,10 @@ user permissions; consult the chosen guide for host requirements and effects.
## Output and exit status
The default terminal format is TOON. Use `--json` when saving results for a
JSON consumer. Output selection and formatting do not change operation status.
JSON consumer. `--json` is indented on a terminal and compact when piped or
redirected, which keeps large results smaller for agents and scripts; pipe
through `jq .` for indented files. Output selection and formatting do not change
operation status.
| Status | Meaning |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+3 -3
View File
@@ -54,15 +54,15 @@ try {
data: { ...evidence, normalized_result: { payload: [marker] } },
meta: metadata,
};
const skeleton = JSON.stringify(template, null, format === "json" ? 2 : 0);
// The destination is a redirected file, so both formats are compact.
const skeleton = JSON.stringify(template);
const encodedMarker = JSON.stringify(marker);
const markerIndex = skeleton.indexOf(encodedMarker);
assert.ok(markerIndex >= 0);
assert.equal(markerIndex, skeleton.lastIndexOf(encodedMarker));
const prefix = skeleton.slice(0, markerIndex);
const suffix = `${skeleton.slice(markerIndex + encodedMarker.length)}\n`;
const indentation = prefix.slice(prefix.lastIndexOf("\n") + 1);
const separator = format === "json" ? `,\n${indentation}` : ",";
const separator = ",";
const expectedHash = createHash("sha256");
expectedHash.update(prefix);
for (let index = 0; index < count; index += 1) {
+15 -3
View File
@@ -4,16 +4,27 @@ import { bufferedJsonParts, jsonParts } from "../domain/jsonSerialization.js";
type JsonOutputFormat = "json" | "jsonl";
/** Write ordinary JSON without constructing a document-sized string. */
/**
* Write ordinary JSON without constructing a document-sized string.
*
* `json` is indented for a terminal reader. Piped or redirected output, which
* agents and scripts consume, is compact: indentation adds no information and
* measured 16-45% more model tokens on real results. The value is identical.
*/
export const writeJsonOutput = async (
value: unknown,
destination: Writable,
format: JsonOutputFormat = "json",
): Promise<void> => {
for (const part of bufferedJsonParts(jsonOutputParts(value, format)))
for (const part of bufferedJsonParts(
jsonOutputParts(value, format, isTerminal(destination)),
))
await writePart(destination, part);
};
const isTerminal = (destination: Writable): boolean =>
"isTTY" in destination && destination.isTTY === true;
const writePart = (destination: Writable, part: string): Promise<void> =>
new Promise((resolve, reject) => {
const onError = (cause: unknown): void => reject(cause);
@@ -39,6 +50,7 @@ const writePart = (destination: Writable, part: string): Promise<void> =>
export function* jsonOutputParts(
value: unknown,
format: JsonOutputFormat,
pretty = true,
): Generator<string> {
if (format === "jsonl" && Array.isArray(value)) {
for (const item of value) {
@@ -47,6 +59,6 @@ export function* jsonOutputParts(
}
return;
}
yield* jsonParts(value, format === "json");
yield* jsonParts(value, pretty && format === "json");
yield "\n";
}
@@ -83,18 +83,22 @@ it("reports real HAR heap exhaustion through CLI and MCP and preserves server us
});
expect(JSON.stringify(response)).not.toContain("caller-marked");
expect(JSON.stringify(response)).not.toContain("rea doctor");
await expect(
execute(process.execPath, [
cli,
"inspect-web-network-capture",
path,
"har",
"--json",
]),
).rejects.toMatchObject({
code: 1,
stdout: expect.stringContaining('"code": "resource_constraint"'),
});
const failed: unknown = await execute(process.execPath, [
cli,
"inspect-web-network-capture",
path,
"har",
"--json",
]).then(
() => undefined,
(cause: unknown) => cause,
);
expect(failed).toMatchObject({ code: 1 });
const stdout =
typeof failed === "object" && failed !== null && "stdout" in failed
? String(failed.stdout)
: "";
expect(JSON.parse(stdout)).toMatchObject({ code: "resource_constraint" });
const small = join(root, "small.har");
await writeFile(small, capture(2));
await client.ping();
+15
View File
@@ -16,6 +16,7 @@ import { silentLogger } from "../../../src/logger.js";
const collect = async (
value: unknown,
format: "json" | "jsonl" = "json",
terminal = true,
): Promise<string> => {
const chunks: Buffer[] = [];
const destination = new Writable({
@@ -28,6 +29,7 @@ const collect = async (
callback();
},
});
if (terminal) Object.defineProperty(destination, "isTTY", { value: true });
await writeJsonOutput(value, destination, format);
return Buffer.concat(chunks).toString("utf8");
};
@@ -52,6 +54,16 @@ describe("incremental CLI JSON", () => {
expect(await collect(value)).toBe(`${JSON.stringify(value, null, 2)}\n`);
});
it("indents JSON only for a terminal and writes piped JSON compactly", async () => {
const value = { nested: [{ value: null }], text: "雪𝟠" };
expect(await collect(value, "json", true)).toBe(
`${JSON.stringify(value, null, 2)}\n`,
);
expect(await collect(value, "json", false)).toBe(
`${JSON.stringify(value)}\n`,
);
});
it("writes compact JSONL records and preserves an empty record set", async () => {
const value = [{ text: "first\nrecord" }, { text: "雪𝟠" }];
expect(await collect(value, "jsonl")).toBe(
@@ -78,6 +90,7 @@ describe("incremental CLI JSON", () => {
}, 1);
},
});
Object.defineProperty(destination, "isTTY", { value: true });
await writeJsonOutput({ leaf }, destination);
expect(largestChunk).toBeLessThan(Buffer.byteLength(leaf));
const actual = Buffer.concat(chunks).toString("utf8");
@@ -212,6 +225,8 @@ describe("streamed CLI result surface", () => {
expect(operation).toBe("inspect_managed_members");
return value;
});
// A terminal destination exercises the indented encoding past the limit.
Object.defineProperty(destination, "isTTY", { value: true });
const argv = ["inspect-managed-members", "fixture.dll", "--json"];
const output = createStreamedCliJsonOutput(argv, destination);
if (output === undefined) throw new Error("Missing JSON output surface");