Skip to content

Blocked edit tool result is rendered twice after interactive tool_call confirmation #3830

@pahar0

Description

@pahar0

What happened?

When an extension intercepts an edit tool call, waits for user input via ctx.ui.select() / ctx.ui.input(), and then blocks the tool with { block: true, reason }, the TUI renders the blocked edit result twice.

The session JSONL contains only one toolResult for that toolCallId, so this appears to be a visual/rendering duplication, not two actual tool results.

Steps to reproduce

  1. Use an extension that intercepts edit tool calls in pi.on("tool_call"), asks the user for confirmation with ctx.ui.select() or ctx.ui.input(), and then returns { block: true, reason: "..." } when the user rejects the edit.
  2. Ask the model to edit a file that already exists, for example: edit test.txt and change one word.
  3. When the extension confirmation UI appears, reject/block the edit and enter any note.
  4. Observe the TUI output for the blocked edit result twice:
Image

pi-session-2026-04-27T17-04-03-181Z_019dcfe5-e46c-715f-a838-3284e59b6acc.html

For the repro, i used this simple extension:

import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";

export default function mutationGuardRepro(pi: ExtensionAPI) {
        pi.on("tool_call", async (event, ctx) => {
                if (!isToolCallEventType("edit", event)) return undefined;

                const path = event.input.path;

                if (!ctx.hasUI) {
                        return {
                                block: true,
                                reason: `Edit ${path} blocked: confirmation requires UI.`,
                        };
                }

                const choice = await ctx.ui.select(`Confirm edit ${path}`, [
                        "Allow",
                        "Block with note",
                ]);

                if (choice === "Allow") {
                        return undefined;
                }

                const note = (await ctx.ui.input("Reject note", "Type note")) ?? "";

                return {
                        block: true,
                        reason: `Mutation rejected by user for edit ${path}. User note: ${note.trim() || "none"}`,
                };
        });
}

Expected behavior

The blocked edit result should be rendered once.

Version

0.70.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginprogressIssue is being worked on

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions