This is a Pi coding agent session from the pi-packages monorepo, dated 2026-05-18. The agent (Claude Opus) was executing issue #54 — decompose a 1,619-line src/index.ts in the pi-subagents package into smaller modules.
The session runs the full /plan-issue → /tdd-plan flow. Four TDD cycles complete cleanly (RED → GREEN → commit), extracting tools/helpers.ts, renderer.ts, notification.ts, and tools/get-result-tool.ts. Then TDD step 5 — extracting the steer_subagent tool — hits a wall.
The file contains a middle dot character (·, U+00B7) in a template literal:
stateParts.join(" · ")The edit tool cannot match text containing this character. The agent diagnoses the problem and tries multiple workarounds. When it attempts to write to /tmp/, the user — watching the agent flail — denies the permission prompt to force a conversation about what's actually going wrong. The session is eventually aborted mid-Python-command.
| Lines | Phase | What happens |
|---|---|---|
| 1–37 | Plan | Agent reads issue #54, studies the 1,619-line index.ts, writes a 7-step decomposition plan, commits it. |
| 38–82 | TDD Step 1 | Extract tools/helpers.ts — clean RED → GREEN → full suite (379 tests) → commit. |
| 83–97 | TDD Step 2 | Extract renderer.ts — clean RED → GREEN → full suite (387 tests) → commit. |
| 98–154 | TDD Step 3 | Extract notification.ts — one test fix (toEndWith isn't a Vitest matcher), then GREEN → full suite (410 tests) → commit. |
| 155–172 | TDD Step 4 | Extract tools/get-result-tool.ts — clean RED → GREEN → full suite (419 tests) → commit. |
| 173–206 | TDD Step 5 | Extract tools/steer-tool.ts — the unicode incident. See below. |
Line 184–185 — First edit attempt fails.
The agent tries a two-part edit: add an import and replace the 53-line inline tool definition with a factory call. The second edit's oldText includes the tool's execute callback, which contains stateParts.join(" · "). Response:
Could not find edits[1] in packages/pi-subagents/src/index.ts. The oldText must match exactly including all whitespace and newlines.
Line 188–189 — Agent re-reads the file to get the exact text. The read tool returns the steer tool code starting at line 717.
Line 190–191 — Second edit attempt fails.
The agent constructs a large oldText from the freshly-read content. Same error:
Could not find the exact text in packages/pi-subagents/src/index.ts.
Line 192–193 — cat -A fails on macOS.
cat: illegal option -- A
usage: cat [-belnstuv] [file ...]
Line 194–195 — Agent uses od -c to inspect the raw bytes. The od output reveals the middle dot at octal offset 0000240:
0000220 e : $ { s t a t e P a r t s .
0000240 j o i n ( " · ** " ) } ` , \n
Line 196 — The diagnosis:
The middle dot is a UTF-8
·(U+00B7). Let me use a different approach — extract by line numbers.
Line 198–199 — head/tail to /tmp/ — user denies.
The agent tries to split the file via head -716 src/index.ts > /tmp/index_before.ts && tail -n +770 src/index.ts > /tmp/index_after.ts.
pi-permission-system prompts for external directory access to /tmp/. The user, who has been watching the agent struggle through repeated edit failures, denies the permission request and uses the denial message to ask: "What is the problem you're encountering with edit?"
Line 200–201 — Third edit attempt — user intervenes again.
The agent tries a smaller oldText (just the first 4 lines of the tool block). The user again asks what's wrong before allowing it to continue.
Line 202 — Agent explains the problem to the user:
The
Edittool is failing because the middle dot character·(U+00B7) in the steer tool'sstateParts.join(" · ")line isn't matching my replacement string. I tried\xb7and\u00b7escape sequences but neither matches the literal UTF-8 byte in the file.
Line 204–205 — Fourth edit succeeds, but it's messy. The agent uses a small unique anchor to partially replace the tool block, leaving dangling old code that needs cleanup.
Line 206 — Agent resorts to Python to reconstruct the file by line numbers:
python3 -c "
lines = open('src/index.ts').readlines()
before = lines[:716]
after = lines[769:]
replacement = [
' // ---- steer_subagent tool ----\n',
' pi.registerTool(defineTool(createSteerTool({\n',
...
]
open('src/index.ts', 'w').writelines(before + replacement + after)
"The session is aborted mid-execution ("stopReason":"aborted"). The Python command never completes.
- This is the same class of bug as the em-dash session — the edit tool's text-matching logic cannot round-trip certain Unicode characters between JSON-encoded
oldTextand the file on disk. - The user used pi-permission-system's external-directory prompt as a conversational interrupt — denying the
/tmp/write not because/tmp/was dangerous, but to force the agent to stop and explain what was going wrong. The denial message field doubled as a question. - This is a good example of the permission prompt serving as a human-in-the-loop checkpoint beyond its original security purpose.
- Four clean TDD cycles completed before the incident, demonstrating that the decomposition plan itself was sound — the unicode character was a tooling obstacle, not a design problem.
- The agent's eventual approach (Python line-number splicing) is a reasonable workaround, but the session was aborted before it could complete.