#3240 · Pi edit events are normalized as additions
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
| Claim | Status | Evidence |
|---|---|---|
| A Pi edit-batch event is normalized as a file addition. | Verified | The focused assertion expected update but received add in both clean checkouts. |
The provider’s real event shape nests replacement pairs under args.edits. | Verified | The 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. | Verified | The translator’s raw item.open delta already contains the incorrect kind before assembly or UI rendering. |
Content-only write operations remain classified as additions. | Verified | The existing focused provider test asserts that behavior, and the proposed change does not alter it. |
3. Environment
- Trusted repository:
get-bb/bbat06aeaa994942ae7527dc49d2268c1f801e8542a0, fetched fromorigin/main. - Darwin 25.6.0 arm64, Node.js 22.22.3, pnpm 9.15.0, Vitest 4.1.1.
- The workspace pins
@earendil-works/pi-coding-agent0.84.0 for this plugin. pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run buildsucceeded independently in both detached checkouts.- No BB server, host daemon, browser, provider process, port, account, or persistent data directory was used.
4. Minimal reproduction
- Check out trusted commit
06aeaa994942ae7527dc49d2268c1f801e8542a0. - Run
pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run build. - Save the test below as
plugins/provider-pi/src/edit-kind.repro.test.ts. - 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.
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.