← reports

#3240 · Pi edit events are normalized as additions

Bug Medium Effort: Low providers provider-pi open on GitHub 2026-09-08 · base 06aeaa9

Verdict: REPRODUCED · Root-cause confidence: high

1. TL;DR

Pi reports multi-replacement file edits with the operation name edit and a nested edits array. BB’s Pi translator accepts that payload but decides whether the file was added or updated solely from an optional top-level oldText field. The nested shape therefore becomes kind: "add", even though the operation is explicitly an edit. Two clean translator-level runs reproduced the mismatch, and the checked-in provider recording shows the same emitted shape.

2. Claims vs findings

ClaimStatusEvidence
A Pi edit-batch event is normalized as a file addition.VerifiedThe focused assertion expected update but received add in both clean checkouts.
The provider’s real event shape nests replacement pairs under args.edits.VerifiedThe trusted checked-in Pi recording contains an edit start event with an edits array and the corresponding runtime delta contains kind: "add".
The incorrect classification originates in the Pi translator rather than later timeline rendering.VerifiedThe translator’s raw item.open delta already contains the incorrect kind before assembly or UI rendering.
Content-only write operations remain classified as additions.VerifiedThe existing focused provider test asserts that behavior, and the proposed change does not alter it.

3. Environment

4. Minimal reproduction

  1. Check out trusted commit 06aeaa994942ae7527dc49d2268c1f801e8542a0.
  2. Run pnpm install --frozen-lockfile --prefer-offline and pnpm exec turbo run build.
  3. Save the test below as plugins/provider-pi/src/edit-kind.repro.test.ts.
  4. Run:
    pnpm exec turbo run test --filter=bb-plugin-provider-pi -- --run src/edit-kind.repro.test.ts

Expected

Test Files  1 passed (1)
Tests       1 passed (1)

Actual, checkout A

FAIL  src/edit-kind.repro.test.ts > classifies Pi edit batches as updates
AssertionError: expected [...] to deep equally contain ObjectContaining{…}

Expected: "kind": "update"
Received: "kind": "add"

Test Files  1 failed (1)
Tests       1 failed (1)

Reproduction test

import { expect, it } from "vitest";
import { createPiDeltaTranslator } from "./delta-translation.js";

it("classifies Pi edit batches as updates", () => {
  const translator = createPiDeltaTranslator({
    resolveModelContextWindow: () => null,
  });

  const deltas = translator.translate(
    {
      type: "tool_execution_start",
      toolCallId: "edit-batch",
      toolName: "edit",
      args: {
        path: "fixture.ts",
        edits: [{ oldText: "before", newText: "after" }],
      },
    },
    { threadId: "thread-one" },
  );

  expect(deltas).toContainEqual(
    expect.objectContaining({
      kind: "item.open",
      item: {
        type: "fileChange",
        changes: [{ path: "fixture.ts", kind: "update" }],
      },
    }),
  );
});

Verification in checkout B

A second detached checkout at the same trusted commit received its own frozen install and full build. The identical focused command again failed one test, with expected update and received add. No report claim required correction.

5. Root cause

The file-operation argument schema defines only top-level path, oldText, newText, and content fields. It is permissive, so an edits array is accepted, but the classifier does not inspect it.

The classifier handles edit and write in one branch and uses only the presence of top-level oldText to choose update versus add:

if (PI_FILE_CHANGE_TOOL_NAMES.has(toolName)) {
  const parsed = piFileEditArgsSchema.safeParse(args);
  ...
  kind: parsed.data.oldText === undefined ? "add" : "update",
}

The checked-in end-to-end Pi recording supplies toolName: "edit" with nested replacements and records kind: "add" at both open and close. Because that wrong kind exists in the translator delta, downstream assembly and timeline presentation faithfully display the wrong operation rather than creating the bug themselves.

6. Proposed fix (first principles)

Classify by the provider operation’s semantics: an edit tool call targets an existing file and should always emit kind: "update"; a content-only write call should retain the existing add behavior. Keep extraction of optional top-level diff text unchanged for legacy payloads. Add the recorded edit-batch shape to the existing translator regression suite and retain the content-only write assertion.

7. Related issues

A review of the repository’s issues carrying the provider-pi label found no other report about this classification path. No open pull request is linked to this issue, and a direct open-PR metadata search found none.

8. Appendix

Commands run

git fetch origin main
pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
pnpm exec turbo run test --filter=bb-plugin-provider-pi -- --run src/edit-kind.repro.test.ts
git log -- plugins/provider-pi/src/delta-translation.ts
git blame -L 235,275 -- plugins/provider-pi/src/delta-translation.ts

Raw translator mismatch

Expected item:
{ type: "fileChange", changes: [{ path: "fixture.ts", kind: "update" }] }

Received item:
{ type: "fileChange", changes: [{ path: "fixture.ts", kind: "add" }] }

The issue content and its links were treated as untrusted data. No command, script, patch, branch, binary, or external URL from the issue was executed or fetched.