---
issue: 583
issue_title: "pi-permission-system: bare-slash `find /` bypasses the external_directory gate"
---

# Retro: #583 — bare-slash `find /` bypasses the external_directory gate

## Stage: Planning (2026-07-13T00:00:00Z)

### Session summary

Traced the `find /` bypass to the `/^\/+$/` bare-slash branch in `rejectNonPathToken` (`src/access-intent/bash/token-classification.ts`), the single point that drops `/`, `//`, `///` before all three bash classifiers.
Confirmed downstream resolution needs no change — a `/` candidate resolves external (`isBoundaryOutsideWorkingDirectory("/")` is `true`, `/` is not a safe system path) and `//`/`///` normalize to `/`.
Wrote a single-cycle `fix:` plan that removes the branch and inverts the encoding tests.

### Observations

- Classified as a non-breaking `fix:`, not `fix!:` — matches sibling gate-tightening fixes #481 and #490, which both added new prompts under `fix:`.
  No config default changes; the fix only makes the `external_directory` gate honor its already-documented `ask` default where a token escaped it.
- The `echo /` now-prompts behavior change is deliberate and consistent with the command-agnostic path model (`echo /etc/passwd` already prompts).
  Surfaced it explicitly in Risks rather than treating it as an alternative; no `ask-user` gate needed since author is the operator and the direction is unambiguous.
- The two `bash-external-directory.test.ts` "guard is still needed" tests encode the removed branch as necessary defense-in-depth — they are deleted, not migrated, since their premise is now false.
- Left the historical plan `0533-win32-git-bash-posix-paths.md:151` parenthetical about the bare-slash rejection unchanged (completed plan record; its `//server/share` conclusion stays correct).
- Ships independently — no roadmap step references #583.

## Stage: Implementation — TDD (2026-07-13T19:41:00Z)

### Session summary

One red→green→commit cycle implemented the whole fix: removed the `/^\/+$/` bare-slash branch from `rejectNonPathToken` (`src/access-intent/bash/token-classification.ts`) so a bare `/`, `//`, `///` reaches the external_directory and path surfaces, and inverted/rewrote the tests that encoded the dropped behavior.
Tidy-First assessor reported no preparatory tidying warranted (single-branch subtraction, already-atomic tests).
Test count unchanged at 2387 (net-zero: 2 unit tests inverted in place, 7 integration tests rewritten 1:1 including a new `find /` regression test replacing the two deleted "guard is still needed" tests).

### Observations

- Empirically confirmed during GREEN that `//` and `///` normalize to lexical `/`, so the resolved external set is `["/"]` for all three — the plan's predicted expectation held exactly.
- No deviations from the plan. `find /` now resolves to external root `["/"]`; verified `pnpm run check`, root `pnpm run lint`, full `pnpm run test` (2387), and `pnpm fallow dead-code` all green; no lockfile changes.
- Pre-completion reviewer: WARN (single non-blocking finding) → resolved.
  It caught a stale "seven shared rejection cases" comment in the test file header (line 13) that mirrored the source-module comment I had already corrected to "six"; fixed and amended into the fix commit before it was pushed.
- Deliberately left the historical plan `0533-win32-git-bash-posix-paths.md` parenthetical unchanged, as the plan specified.

## Stage: Final Retrospective (2026-07-13T20:05:00Z)

### Session summary

A single session carried #583 end-to-end: live-repro bug confirmation, issue authoring, planning, one-cycle TDD, and shipping `pi-permission-system` v20.5.0.
The root cause (the `/^\/+$/` branch in `rejectNonPathToken`) was diagnosed on the first trace, the plan predicted the `["/"]` resolution exactly, and TDD landed with zero deviations.
Friction was minimal — one absolute-path typo (caught by the very gate under repair) and one mirror-comment miss (caught by the pre-completion reviewer before push).

### Observations

#### What went well

- Clean diagnosis-to-fix arc: the bug was traced to a single predicate branch on first read, and every downstream prediction held — `//`/`///` normalizing to lexical `/`, the `["/"]` external set, and the net-zero test count were all correct in the plan before implementation.
- The pre-completion reviewer earned its keep: it caught a stale "seven shared rejection cases" comment in `token-classification.test.ts:13` that mirrored the source-module header I *had* corrected ("seven" → "six") but whose test-file twin I missed.
  Caught before push, fixed via amend — no `style:` follow-up commit needed.
- Correct handling of the release-please merge edge case: `release_pr_merge` refused with `UNSTABLE`, but `statusCheckRollup` showed a real `check` job `IN_PROGRESS` (not the empty-rollup `GITHUB_TOKEN` case), so I waited two poll cycles for it to finish and retried `release_pr_merge` rather than falling back to a manual `gh pr merge` while a check was running — exactly the step-6.4 protocol.

#### What caused friction (agent side)

- `other` (absolute-path typo) — during planning a `Read` used `/Users/chris/development/pi/pi-permission-system/packages/pi-permission-system/test/bash-external-directory.test.ts`, collapsing `pi-packages` into `pi-permission-system`.
  The permission system denied it as an `external_directory` access.
  Impact: one retry with the corrected path, no rework.
  Notable irony — the gate under repair caught my own mistake; a reminder that relative paths avoid this entirely.
- `other` (mirror-comment miss) — fixed the rejection-case count in the source module header but not the identical comment in the sibling test file; relied on the reviewer to catch it.
  Impact: one amend into the unpushed fix commit, no rework.

#### What caused friction (user side)

- None.
  The user's interventions were well-placed: confirming the bug reproduced ("Do we agree we have a bug?"), then delegating the standard flow.
  No mechanical oversight was required.

### Diagnostic details

- **Model-performance correlation** — both subagents (`tidy-first-assessor`, `pre-completion-reviewer`) ran on `anthropic/claude-sonnet-5`, appropriate for read-only judgment work; the reviewer's WARN finding confirms the model was capable of the self-consistency check.
  No mismatch.
- **Escalation-delay tracking** — no `rabbit-hole` friction; the longest same-target loop was the deliberate 3-cycle poll of the release PR's in-progress check, which is correct protocol, not an escalation delay.
- **Unused-tool detection** — none missed. `grep`/`Read` were the right tools for a single-symbol trace (`rejectNonPathToken`, `^\/+$`); a semantic `colgrep` would have added noise.
- **Feedback-loop gap analysis** — verification was incremental: RED confirmed before GREEN, affected-file tests during the cycle, then full `test`/`check`/`lint`/`fallow` after.
  No end-only batching.

### Changes made

1. Appended this Final Retrospective stage entry to `packages/pi-permission-system/docs/retro/0583-bare-slash-root-external-directory.md`.
2. No `AGENTS.md` or `.pi/prompts/` changes — the operator confirmed retro-file-only; both friction points were one-retry, no-rework, and already absorbed by existing safety nets (the `external_directory` gate and the pre-completion reviewer).
