Skip to content

Commit 4b28396

Browse files
committed
Prepend the secret scrub and result cap so short-circuiting plugins can't skip them
composeMiddleware wraps outer-to-inner in array order, so a plugin positioned earlier in buildCorePosixToolPlugins still sees a call's final result even when a later plugin (ripgrepPlugin) answers directly without invoking its own next(). Move toolResultSecretScrubPlugin and resultTruncationPlugin to the front of the array so both are unconditional outer wrappers around the entire chain, mirroring how vendor/intx-inference/src/assembly.ts hardcodes its size-cap transform as the first, mandatory element rather than trusting every middleware author to call next(). This closes CL-5717: grep output (and anything else a future plugin answers without delegating) is now capped and scrubbed regardless of where in the chain it short-circuits.
1 parent 99a2023 commit 4b28396

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

src/agent/posix-tool-plugins.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,18 @@ export type CorePosixToolPluginsArgs = {
3535
};
3636

3737
// Middleware order matches docs/ARCHITECTURE.md: path escape through truncation,
38-
// with shell-guard after permission so blocked commands never spawn. Secret-shaped
39-
// result scrub runs immediately before truncation so credentials are redacted first.
38+
// with shell-guard after permission so blocked commands never spawn.
39+
//
40+
// The secret scrub and the character cap are prepended unconditionally, ahead
41+
// of every other plugin, rather than left in call order. composeMiddleware
42+
// wraps outer-to-inner in array order, so a plugin earlier in this array
43+
// still observes the final result even when a later plugin (ripgrepPlugin,
44+
// notably) answers a call directly without invoking its own `next()` and so
45+
// never reaches whatever sits after it. A mandatory terminal concern like
46+
// redacting a credential cannot depend on every middleware author remembering
47+
// to call `next()` — see vendor/intx-inference/src/assembly.ts's
48+
// sizeCapTransform for the same reasoning upstream. Scrub sits outermost so it
49+
// runs on the already-capped content, matching the previous in-chain order.
4050
export function buildCorePosixToolPlugins(args: CorePosixToolPluginsArgs): ToolPlugin[] {
4151
const {
4252
cwd,
@@ -47,6 +57,8 @@ export function buildCorePosixToolPlugins(args: CorePosixToolPluginsArgs): ToolP
4757
shellEnv,
4858
} = args;
4959
return [
60+
toolResultSecretScrubPlugin(),
61+
resultTruncationPlugin(),
5062
pathEscapePlugin(cwd, createWorktreeRootsProvider(cwd)),
5163
deleteFilePlugin(cwd),
5264
toolOutputUriPlugin(),
@@ -65,8 +77,6 @@ export function buildCorePosixToolPlugins(args: CorePosixToolPluginsArgs): ToolP
6577
editFileDiagnosticsPlugin(),
6678
lspHintPlugin(),
6779
createLSPPlugin({ cwd, minSeverity: 1 }),
68-
toolResultSecretScrubPlugin(),
69-
resultTruncationPlugin(),
7080
...extraToolPlugins,
7181
];
7282
}

src/plugins/result-truncation-plugin.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ export const MAX_RESULT_CHARS = 80_000;
99
// The single primitive for size truncation: callers may pass their own
1010
// threshold but never invent their own wording, so a result can never carry
1111
// two differently-worded "truncated" notices. Called directly by runners this
12-
// middleware does not wrap — the MCP tool runner (src/mcp/plugin.ts), and
13-
// ripgrep-plugin.ts, which answers grep without calling next and so never
14-
// reaches this middleware despite sitting earlier in the same plugin array.
12+
// middleware does not wrap — the MCP tool runner (src/mcp/plugin.ts). The
13+
// posix chain gets this middleware prepended unconditionally in
14+
// posix-tool-plugins.ts, so plugins like ripgrepPlugin that answer without
15+
// calling next() no longer need to apply the cap themselves.
1516
export function truncateToolResultContent(
1617
content: string,
1718
maxChars: number = MAX_RESULT_CHARS,

0 commit comments

Comments
 (0)