thiagowfx's avatar

Β¬ just serendipity πŸ€ (not just serendipity)

pi: prompt before dangerous commands

β€’ 251 words β€’ 2 min

Previously.

Problem statement: my Pi extension blocked dangerous shell commands outright, even when I had already preserved the state they could destroy.

The guard parses every bash tool call with tree-sitter, catching commands such as rm -rf, terraform destroy, and destructive Git operations. Today it did exactly what I had asked:

shell
% git -C "$(brew --repo thiagowfx/pancake)" reset --hard origin/master
git reset --hard is blocked - discards changes irreversibly

Blocking remains the right default, but an interactive session can ask me. Pi extensions can intercept the tool_call event and open a confirmation dialog through ctx.ui.

The change in dangerous-command-guard/guard.ts:

diff
-  pi.on("tool_call", async (event) => {
+  pi.on("tool_call", async (event, ctx) => {
     if (event.toolName !== "bash") return;

     const command = event.input.command;
     if (typeof command !== "string") return;

     const blocked = await findBlockedCommand(command);
-    if (blocked) return { block: true, reason: blocked.reason };
+    if (!blocked) return;
+    if (!ctx.hasUI) return { block: true, reason: blocked.reason };
+
+    const allowed = await ctx.ui.confirm(
+      "Allow dangerous command?",
+      `${blocked.command}\n\n${blocked.reason}`,
+    );
+    if (!allowed) return { block: true, reason: "Blocked by user" };
   });

Interactive Pi now pauses for approval. Print and JSON modes have no UI, so they still fail closed. Safe commands never prompt.

The tests exercise all three decisions:

text
βœ” extension prompts before dangerous bash tool calls (0.66225ms)
β„Ή tests 48
β„Ή pass 48
β„Ή fail 0

Same guardrails, with an escape hatch operated by a human rather than a model.


πŸ€– Drafted with /bloggify.