Skip to content

Commit 2bc55f2

Browse files
skoob13claude
andcommitted
fix(agent): prompt for destructive PostHog exec sub-tools in auto mode
Auto mode only promises hands-off file edits and shell commands, but the PostHog exec destructive gate short-circuited to silent allow in auto mode, so update/delete/destroy sub-tools were the only exec calls that never produced a permission_request. Non-destructive exec calls already prompt in auto mode (and web clients auto-approve them client-side), which made the gate exactly backwards on the cloud surface: safe calls asked, destructive ones ran silently. Keep the bypass for bypassPermissions only; persisted allow_always sub-tool approvals still skip the prompt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9beff6e commit 2bc55f2

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

packages/agent/src/adapters/claude/permissions/permission-handlers.test.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ describe("canUseTool MCP approval enforcement", () => {
200200
expect(result.behavior).toBe("allow");
201201
});
202202

203-
it("bypasses the PostHog exec gate in auto mode", async () => {
203+
it("prompts through the PostHog exec gate in auto mode", async () => {
204204
setMcpToolApprovalStates({ mcp__posthog__exec: "approved" });
205205
const hasApproval = vi.fn().mockReturnValue(false);
206206
const addApproval = vi.fn().mockResolvedValue(undefined);
@@ -215,12 +215,23 @@ describe("canUseTool MCP approval enforcement", () => {
215215
addPostHogExecApproval: addApproval,
216216
},
217217
},
218+
client: createClient({
219+
outcome: { outcome: "selected", optionId: "allow" },
220+
}),
218221
});
219222
const result = await canUseTool(context);
220223

224+
// Auto mode only auto-approves file edits and shell commands; destructive
225+
// PostHog sub-tools must still reach the client as a permission prompt.
221226
expect(result.behavior).toBe("allow");
222-
expect(context.client.requestPermission).not.toHaveBeenCalled();
223-
expect(hasApproval).not.toHaveBeenCalled();
227+
expect(hasApproval).toHaveBeenCalledWith("experiment-update");
228+
expect(context.client.requestPermission).toHaveBeenCalledWith(
229+
expect.objectContaining({
230+
toolCall: expect.objectContaining({
231+
title: "The agent wants to run `experiment-update` on PostHog",
232+
}),
233+
}),
234+
);
224235
expect(addApproval).not.toHaveBeenCalled();
225236
});
226237

packages/agent/src/adapters/claude/permissions/permission-handlers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -778,10 +778,10 @@ export async function canUseTool(
778778
if (isPostHogExecTool(toolName)) {
779779
const subTool = extractPostHogSubTool(toolInput);
780780
if (subTool && isPostHogDestructiveSubTool(subTool)) {
781-
if (
782-
session.permissionMode === "auto" ||
783-
session.permissionMode === "bypassPermissions"
784-
) {
781+
// "auto" deliberately does NOT skip this gate: the mode only promises
782+
// hands-off file edits and shell commands, and destructive PostHog
783+
// sub-tools must still surface a permission prompt to the client.
784+
if (session.permissionMode === "bypassPermissions") {
785785
return {
786786
behavior: "allow",
787787
updatedInput: toolInput as Record<string, unknown>,

0 commit comments

Comments
 (0)