From 663dd5a44e0232eda2821a6e4431e8ab1705caf5 Mon Sep 17 00:00:00 2001 From: saurabh Date: Thu, 29 Jan 2026 20:20:05 +0700 Subject: [PATCH] feat(exec): integrate pre-exec hooks into exec tool Calls checkPreExecApproval() before running any shell command. If a hook denies the command, throws an error with the hook name and denial reason instead of executing. --- src/agents/bash-tools.exec.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index b9de81872..6330283f2 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -19,6 +19,7 @@ import { resolveExecApprovals, resolveExecApprovalsFromFile, } from "../infra/exec-approvals.js"; +import { checkPreExecApproval } from "../infra/pre-exec-hooks.js"; import { requestHeartbeatNow } from "../infra/heartbeat-wake.js"; import { buildNodeShellCommand } from "../infra/node-shell.js"; import { @@ -755,6 +756,22 @@ export function createExecTool( throw new Error("Provide a command to start."); } + // Pre-exec hooks check (workspace-level command validation) + const workspaceDir = defaults?.cwd || process.cwd(); + const preExecResult = await checkPreExecApproval({ + workspaceDir, + toolName: "exec", + command: params.command, + workdir: params.workdir, + env: params.env, + }); + if (!preExecResult.allowed) { + const hookInfo = preExecResult.hookName ? ` (hook: ${preExecResult.hookName})` : ""; + throw new Error( + `Command blocked by pre-exec hook${hookInfo}: ${preExecResult.reason || "denied"}` + ); + } + const maxOutput = DEFAULT_MAX_OUTPUT; const pendingMaxOutput = DEFAULT_PENDING_MAX_OUTPUT; const warnings: string[] = [];