Skip to content

Commit 0ca6637

Browse files
committed
feat(agent): reduce bash output token spend
Generated-By: PostHog Code Task-Id: 272f46f4-223e-41c5-8bd3-681e5d96028f
1 parent 0d728bc commit 0ca6637

12 files changed

Lines changed: 511 additions & 457 deletions

File tree

packages/agent/src/adapters/claude/git-command.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
// Pure git command-line parsing, shared by the signed-commit guard (hooks.ts)
2-
// and the RTK rewrite (session/rtk.ts). Kept dependency-free so importers don't
3-
// drag in the hooks module's heavier import chain.
1+
// Pure git command-line parsing used by the signed-commit guard. Kept
2+
// dependency-free so importers don't drag in the hooks module's heavier chain.
43

54
// git global options that consume the following token as their value, so the
65
// subcommand detector must skip both (mirrors the sandbox `git` PATH shim).

packages/agent/src/adapters/claude/session/options.test.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,16 @@ describe("buildSessionOptions", () => {
210210
expect(healSpy).not.toHaveBeenCalled();
211211
});
212212

213-
describe("rtk and signed-commit guard ordering", () => {
213+
describe("rtk hook registration", () => {
214214
const originalRtk = process.env.POSTHOG_RTK;
215215
let dir: string;
216216
let binary: string;
217217

218218
beforeEach(() => {
219219
dir = fs.mkdtempSync(path.join(os.tmpdir(), "rtk-order-"));
220220
binary = path.join(dir, "rtk");
221-
fs.writeFileSync(binary, "#!/bin/sh\n");
221+
fs.writeFileSync(binary, "#!/bin/sh\necho 'rtk 0.43.0'\n");
222+
fs.chmodSync(binary, 0o755);
222223
process.env.POSTHOG_RTK = binary;
223224
});
224225

@@ -244,7 +245,7 @@ describe("buildSessionOptions", () => {
244245
};
245246
};
246247

247-
it("registers the signed-commit guard before the rtk rewrite so the guard evaluates raw commands (cloud)", async () => {
248+
it("keeps the signed-commit guard and pnpm test rewrite active in cloud mode", async () => {
248249
const options = buildSessionOptions({
249250
...makeParams(),
250251
cloudMode: true,
@@ -254,9 +255,6 @@ describe("buildSessionOptions", () => {
254255
);
255256
const opts = { signal: new AbortController().signal };
256257

257-
// Identify each hook behaviorally: the guard denies `git commit`, the
258-
// rtk hook rewrites `git status`. Their registration order is the
259-
// defense-in-depth guarantee that the guard always sees the raw command.
260258
let guardIndex = -1;
261259
let rtkIndex = -1;
262260
for (const [index, hook] of hooks.entries()) {
@@ -273,22 +271,46 @@ describe("buildSessionOptions", () => {
273271
}
274272

275273
const rewriteResult = (await hook(
276-
bashInput("git status"),
274+
bashInput("pnpm test"),
277275
undefined,
278276
opts,
279277
)) as PreToolUseOutput;
280278
if (
281279
rtkIndex === -1 &&
282280
rewriteResult.hookSpecificOutput?.updatedInput?.command ===
283-
`${binary} git status`
281+
`${binary} test pnpm test`
284282
) {
285283
rtkIndex = index;
286284
}
287285
}
288286

289287
expect(guardIndex).toBeGreaterThanOrEqual(0);
290288
expect(rtkIndex).toBeGreaterThanOrEqual(0);
291-
expect(guardIndex).toBeLessThan(rtkIndex);
289+
});
290+
291+
it("applies the shared pnpm test policy through the Claude hook", async () => {
292+
const options = buildSessionOptions({
293+
...makeParams(),
294+
cloudMode: false,
295+
});
296+
const hooks = (options.hooks?.PreToolUse ?? []).flatMap(
297+
(entry) => entry.hooks ?? [],
298+
);
299+
const opts = { signal: new AbortController().signal };
300+
301+
const results = await Promise.all(
302+
hooks.map((hook) => hook(bashInput("pnpm test"), undefined, opts)),
303+
);
304+
305+
expect(results).toContainEqual(
306+
expect.objectContaining({
307+
hookSpecificOutput: expect.objectContaining({
308+
updatedInput: {
309+
command: `${binary} test pnpm test`,
310+
},
311+
}),
312+
}),
313+
);
292314
});
293315
});
294316

packages/agent/src/adapters/claude/session/options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { FileEnrichmentDeps } from "../../../enrichment/file-enricher";
1414
import { IS_ROOT } from "../../../utils/common";
1515
import { buildGatewayPropertyHeaders } from "../../../utils/gateway";
1616
import type { Logger } from "../../../utils/logger";
17+
import { resolveRtkPrefix } from "../../rtk";
1718
import type { TaskState } from "../conversion/task-state";
1819
import {
1920
createPostToolUseHook,
@@ -31,7 +32,7 @@ import type { EffortLevel } from "../types";
3132
import { buildAppendedInstructions } from "./instructions";
3233
import { loadUserClaudeJsonMcpServers } from "./mcp-config";
3334
import { DEFAULT_MODEL, FALLBACK_MODEL } from "./models";
34-
import { createRtkRewriteHook, resolveRtkPrefix } from "./rtk";
35+
import { createRtkRewriteHook } from "./rtk";
3536
import type { SettingsManager } from "./settings";
3637

3738
export interface ProcessSpawnedInfo {

packages/agent/src/adapters/claude/session/rtk.test.ts

Lines changed: 0 additions & 236 deletions
This file was deleted.

0 commit comments

Comments
 (0)