9.4 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 385 | pkg:pi-permission-system — Respect pi default active tool set instead of activating all non-denied tools |
Retro: #385 — Respect pi default active tool set instead of activating all non-denied tools
Stage: Planning (2026-06-11T21:43:29Z)
Session summary
Planned the fix for AgentPrepHandler.handle() activating pi's off-by-default tools (find/grep/ls) in every session.
The fix switches the base set from pi.getAllTools() to pi.getActiveTools(), making the permission system purely restrict-only.
Evaluated the issue author's reference PR #386 and adopted its approach with two improvements: typing getActive(): string[] to match the real SDK contract (PR used unknown[]) and adding an explicit regression test.
Observations
- Confirmed via the SDK
.d.tsthatgetActiveTools()returnsstring[]whilegetAllTools()returnsToolInfo[]. PR #386's test mocks return objects forgetActive, which pass only becausegetToolNameFromValuetolerates both shapes — a fidelity gap the plan fixes by returning bare strings everywhere. PermissionGateHandlerkeepsgetAll()forvalidateRequestedTool(registration checks must see the full registry); onlyAgentPrepHandlerswitches togetActive(). This leaves a latent ISP seam (disjoint consumer slices ofToolRegistry) — recorded as track-and-watch, not split now.- Classified as breaking (confirmed with the user via
ask_user): the main session's effective tool set changes on upgrade without a user edit, sofix!:+BREAKING CHANGE:footer. The restrict-only contract means users wantingfind/grep/lsactive must enable them via pi's ownactiveToolsconfig. - Verified idempotence: starting from the active set makes the operation purely subtractive toward a fixed point, so no oscillation across repeated
before_agent_startfires. - Key risk flagged for TDD: confirm
getActiveTools()is already populated with pi's defaults whenbefore_agent_startfires (lifecycle timing). PR #386's existence suggests the reporter validated this empirically. - Credit: Ben Tang (@0xbentang) reported #385 and authored reference PR #386.
The plan records a
Co-authored-by: Ben Tang <bentang@fastmail.com>trailer for the implementation commits so the credit lands in git history.
Stage: Implementation — TDD (2026-06-11T22:05:26Z)
Session summary
Completed all three planned TDD cycles: (1) added getActive(): string[] to ToolRegistry and wired it to pi.getActiveTools() plus every fixture/fake; (2) added a regression test and switched AgentPrepHandler.handle() from getAll() to getActive() (the breaking fix!:); (3) clarified the restrict-only contract in docs/configuration.md.
Test count went from 1921 to 1922 (+1 regression test); check, lint, and fallow dead-code all green.
Observations
- Plan deviation (benign): the plan's Module-Level Changes listed
test/handlers/external-directory-session-dedup.test.tsandtest/handlers/tool-call.test.tsas needinggetActiveedits, but both consume the sharedmakeToolRegistryfactory, which now supplies a defaultgetActive. Neither file needed touching — TypeScript passing at both call sites confirms the interface is satisfied. This is a small simplification over reference PR #386, which added redundantgetActivestubs to those files. - The regression test (
does not activate registered tools pi left inactive (find/grep/ls)) setsgetActiveto the default four andgetAllto a seven-tool superset, assertingsetActiveis called with exactly the four. It failed cleanly on the oldgetAll()handler (called with all seven) and passed after the switch — the canonical guard for #385. - Pre-completion reviewer: PASS.
One non-blocking WARN: the
allToolsvariable inbefore-agent-start.tsnow holds pi's active subset, so the name misled. Renamed it toactiveToolsand folded the rename into thefix!:commit (viagit reset --soft+--amend, unpushed) rather than a follow-up. - The
fix!:andfeat:commits both carry theCo-authored-by: Ben Tang <bentang@fastmail.com>trailer (verified it survived the amend).
Stage: Final Retrospective (2026-06-11T22:39:40Z)
Session summary
Shipped #385 across planning, TDD, and ship stages: pi-permission-system v11.0.0 released with the restrict-only before_agent_start fix.
The dominant lesson is a missing-context failure — I asserted a non-existent "pi activeTools config" as the breaking-change remediation, which the user had to correct with a follow-up commit (58db6f81); the wrong guidance still ships in the v11.0.0 CHANGELOG and the issue close comment.
Observations
What went well
- Reference-PR evaluation: reading PR #386 alongside the issue, then adopting its approach but improving on it (typing
getActive(): string[]instead of the PR'sunknown[], adding the missing regression test) and crediting the author viaCo-authored-by:trailers. A clean "accept-and-improve" flow rather than rubber-stamping or rewriting. - The regression test design (
getActivereturns the four defaults,getAllreturns a seven-tool superset, assertsetActivecalled with exactly the four) was a precise guard: red on the oldgetAll()handler, green after the switch. - Incremental verification cadence: per-file
vitestafter each red/green,pnpm run checkbefore the interface-touching commits, full suite +lint+fallow dead-codeat the end.
What caused friction (agent side)
missing-context(high impact, user-caught) — Asserted "pi's ownactiveToolsconfiguration" as the way users re-enablefind/grep/ls, without verifying pi's actual tool-activation surface. The real mechanism is the--tools/-tCLI flag (orcreateAgentSession({ tools: [...] })); there is no persistent config-file key. The error propagated to the plan, thefix!:BREAKING CHANGE:footer, and the issue close comment. Impact: the user pushed a correction commit (58db6f81) to the plan; the wrong guidance still ships in the v11.0.0CHANGELOG.md(release-please-owned, generated from the commit footer — not editable) and the GitHub issue #385 close comment.other(low-medium, self-identified) — Commit-split surgery during the crediting sub-task:git reset --soft HEAD~2left both the plan and retro changes staged, so the first recommit swallowed both files; a second attempt split the retro across both commits; a third (git reset --soft HEAD~2then mixedgit reset) finally separated them. Impact: ~2 redo cycles, no shipped defect.other(low, self-identified) — AnEditbatch onsession-start.test.tswas rejected because the two fakeExtensionAPIblocks are identical and my firstoldTextpair was not uniquely anchored; re-anchored on the enclosingtest(...)names. Impact: one retry.
What caused friction (user side)
- The credit request ("I'd like to give credit to 0xbentang, too") arrived after the plan was already committed, which forced the retroactive commit-split surgery above. Opportunity (not criticism): surfacing co-authorship intent during planning would have folded the trailers into the normal commit flow.
- The
activeTools→--toolscorrection was delivered as a direct commit between sessions rather than as a redirect. A one-line "verify how pi activates tools" nudge during planning would have caught the error before it reached the immutable CHANGELOG.
Diagnostic details
- Unused-tool detection — for the
missing-contextfinding,code_searchandweb_searchwere available and never used. A singlecode_search "pi coding agent tool activation --tools CLI flag"would have surfaced the real mechanism before the wrong guidance shipped. I used the SDK.d.tsto confirmgetActiveTools(): string[]but never checked the user-facing activation path. - Model-performance correlation — one subagent dispatched (
pre-completion-reviewer, default model): judgment-heavy review work, 42 tool uses, returned PASS plus a real naming WARN (allTools→activeTools). Appropriate model-to-task fit; no mismatch. - Feedback-loop gap analysis — no gap; verification ran incrementally per TDD step, not only at the end.
- Escalation-delay tracking — no single-error sequence exceeded five consecutive tool calls; the commit-split retries were distinct strategies, not one repeated error.
Changes made
AGENTS.md(Commits section) — added a rule to verify a breaking-change migration mechanism (CLI flag, config key, API call) against the real surface before asserting it, noting the note ships to the uneditable CHANGELOG and the close comment.AGENTS.md(git guidance) — appended a one-line note thatgit reset --soft HEAD~Nstages all N commits together, so re-splitting needs a mixedgit resetfirst.- GitHub issue #385 close comment — corrected the
activeToolsconfig reference to the--tools/-tCLI flag (andcreateAgentSession({ tools })), with an inline correction note. - Known erratum (not fixed): the v11.0.0
CHANGELOG.mdBREAKING CHANGEentry still says "activeTools configuration" — it is generated from thefix!:commit footer and owned by release-please, so it was left as-is rather than hand-edited. - Closed reference PR #386 (superseded by the shipped commits) with a comment crediting @0xbentang and noting the two improvements folded in; the
Co-authored-by:trailers preserve the credit in history.