Proof audit, pass 1, with inspection of the existing captures, for [#32591](https://git.cafe/pingdotgg/t3code/pull/32591).

No blocking proof gap found for this one-branch server change. The existing real-app failure captures, successful setup transcript, and interactive-PTY results cover the minimum evidence set. Two qualifications: the toast hides the exact “544 ms” value in the after image, and the image of the two-line failure cannot itself establish that `ran.txt` is absent; that filesystem check is reported separately in the PR body. Neither qualification removes the visible failure state, Retry button, parse error, or completion marker. No millisecond latency claim is needed to establish this fix.

Reviewed head [12d9a9d011](https://github.com/pingdotgg/t3code/commit/12d9a9d01100fd40cf5d856b6ae7af8fde9a8933), branch `fix/issue-32556`, against current main [0a9abddaee](https://github.com/pingdotgg/t3code/commit/0a9abddaee00ff1495e77a03dd4fafa73c3cd4cb). Remote refs and GitCafe metadata agreed at audit time. The head's parents are the capture commit [f2102e2808](https://github.com/pingdotgg/t3code/commit/f2102e2808a77633a5fecf4b46b83078135f852b) and current main. Both changed files are byte-identical between the capture commit and reviewed head: runner blob `e601d0e420184e1144bb6a1046a2fca3a9e02246`; test blob `1e154d5330460d775ad61f95a29fdae96cce575f`.

I ran the requested baseline-to-main project/terminal diff. Project code is unchanged. TerminalManager's only production change adds `-l` when platform is macOS AND shell is Bash; it does not alter fish on either Linux or macOS. I also checked script selection/input helpers, PTY adapter, setup tracker, setup/settle callers, their layer wiring, and relevant contracts: unchanged. Other direct-dependency changes concern secret persistence, projection history/link metadata (including an equivalent shell-query helper extraction), an added worktree-cache path, and additive shell PATH-install helpers; none changes the captured fish command submission or completion route. Upstream worktree checkout/cache changes precede the unchanged runner handoff. The web setup card and Retry handling are unchanged; the terminal drawer's intervening change concerns touch resize handles. Effect and node-pty versions remain unchanged. This supports carrying the existing evidence forward; it does not assert every upstream file is unchanged.

The changed expression is [the fish wrapper](https://github.com/pingdotgg/t3code/blob/12d9a9d01100fd40cf5d856b6ae7af8fde9a8933/apps/server/src/project/ProjectSetupScriptRunner.ts#L193). It is reached only when completion observation is requested and the terminal's actual spawned shell resolves to fish. Setup and settle select different actions/terminal IDs, then use the same expression. The setup caller's async policy and the settle caller's detached completion handling are unchanged. All clients and connection modes use this server result; there is no changed client render path or provider-specific branch needing another UI capture.

For the table, **captured** means the minimum representative evidence is present in the PR body: images, the runtime transcript, or the reported PTY table, supplemented by routing inspection where explicitly stated. It does not mean every permutation has its own screenshot or that I reran the historical PTY sessions. **not affected** means the changed fish expression is bypassed. There are no required **missing** rows.

Common real-app seed recipe: the lead can start an isolated web dev server with `T3CODE_DEV_SEED=0`, `SHELL` pointing to the fish executable, and an explicit fresh home directory under a new timestamped scratch run. Use a committed synthetic Git repo with a `t3.json` containing `{"scripts":[{"name":"Fixture","command":"<row command>","icon":"configure","runOnWorktreeCreate":true,"async":false}]}`. Add that repo as a project, import the action from `t3.json`, ensure project/default overrides do not replace it, then start a thread in a new worktree. No real provider turn is needed for failures; the free replay provider can cover successful agent release. This audit did not start a server or modify any worktree.

| ID / named fixture | Diff affected? / distinct observable outcome | Required data and how to seed it | Minimum capture and existing evidence | Status |
|---|---|---|---|---|
| F1 — Blocking fish parse failure | Yes. Main stays in setup without a printed sentinel; head reports status 1, preserves the error terminal, blocks agent start and offers Retry. | Common recipe; `node "${HOME:-.}/x.mjs" setup`; `runOnWorktreeCreate:true`, `async:false`; compare baseline and head. The referenced file need not exist because fish rejects parsing first. | Before still shows Setup at 2m 17s with fish error and no **output** sentinel; after still shows failure, Retry, source error and marker ending `:1`. A hang needs persistence evidence: the elapsed-time still plus PTY “no sentinel” result and reported four-minute observation suffice here. An untimed still alone would not. A recording/timed same-run pair would be needed for a timing/animation claim; none is made. | captured |
| F2 — Malformed multiline is atomic | Yes. Status 1 and no execution of the later valid line; avoids a misleading success from evaluating only a valid trailing statement. | Common recipe; `cp "${HOME}/.env.shared" .env` followed by `echo second-line-ran > ran.txt`; fresh worktree without `ran.txt`. Check the file afterwards. Neither a real shared env file nor its contents are needed. | Third still shows both submitted lines, first-line parse error, failure/Retry and marker `:1`. PR body separately reports `ran.txt` absent. PTY table corroborates that neither line runs for its malformed-first-line fixture. No second baseline still is needed: same missing-sentinel outcome as F1. The image alone does not prove filesystem absence. | captured |
| F3 — Valid fish body completes | Yes, wrapper changed; outcome remains status 0, idle setup terminal closes when appropriate and the agent can start. | Common recipe; successful multiline body shown below. LF/CRLF normalization is unchanged. Quotes, backslashes, `set -l`, a loop, and a trailing comment are encoding/body variants of this success fixture. Functions/aliases still resolve in the sourced shell; editor-only abbreviations are excluded from this success contract. | Real-app transcript shows worktree-path output, `sum 6`, and marker `:0`; body reports the agent started/replied. PTY table covers valid multiline/trailing comment, quotes/backslashes, and locals/loop on fish 3.7.0 and 4.3.2. Server-only change: transcript is sufficient; another success screenshot is unnecessary. | captured |
| F4 — Fish nonzero completion / explicit exit | Yes. `false` retains status 1; `exit 3` now reports 3 rather than closing the parent setup shell and producing null completion. | Common recipe, separately `false` and `exit 3`. Same failure UI class as F1, with the corresponding status and retained terminal. A command relying solely on a fish abbreviation now does not expand and can enter this nonzero class; that deliberate limitation is documented. | PTY table has `false` → 1 and `exit 3` → 3 on both fish versions; F1 supplies representative app failure/Retry rendering. Separate screenshot per numeric status is unnecessary. Abbreviation/function/alias behavior is documented and traced to sourcing, not separately demonstrated by an existing capture. | captured |
| F5 — Intentional stdin wait | Yes, wrapper changed; a command actually waiting for stdin remains pending until input, rather than consuming the completion-report text as input. | Common recipe with `read answer`; attach the setup terminal, enter input, and inspect status. Until input, pending setup is intended. | Existing PTY table reports `read` waits on both sides and both fish versions. This is an intentional wait, not the fixed parse-error hang. No new elapsed-time screenshot is required for the minimum set; a claim about a particular wait duration or post-input app flow is not covered. | captured |
| F6 — Async fish setup uses the same completion | Yes, same wrapper; agent may start before completion because `async` defaults true. Nonzero updates the setup stage without the synchronous setup-error gate. | Common recipe with `async:true` or omitted; use F1 or F3 command. New/reused worktree paths both enter the same runner. | F1/F3/PTY evidence establishes the changed completion behavior; unchanged setup caller code establishes async routing. No independent async UI behavior changed, so no extra image is in the minimum set. An async app run was not directly captured. | captured (shared wrapper; routing inspected) |
| F7 — Fish settle action uses the same completion | Yes, same wrapper; settle creates a unique terminal, observes completion detached, closes an idle success terminal and retains nonzero/error output. It does **not** show the setup Retry card. | Common recipe with `runOnSettle:true` instead of `runOnWorktreeCreate`; create a thread with its own existing worktree, stop its turn and settle it. Use F1/F3/F4 commands. An already-processed settlement or no worktree cannot exercise this path. | Same fish PTY/body evidence as F1–F5 plus unchanged settle caller and common wrapper. No separate settle image is needed to prove this one-expression change. A settle app run was not directly captured; setup images are not claimed as settle UI evidence. | captured (shared wrapper; routing inspected) |
| U1 — POSIX shell | No. Same subshell wrapper/status reporting. Actual fallback shell matters; any shell name except fish, PowerShell, or cmd maps here. | Common recipe with server shell Bash/zsh/sh; use commands valid for that shell, including multiline/comments/heredocs. | Diff/source inspection; no new capture required. macOS Bash login-mode change is an upstream main change, outside this PR. | not affected |
| U2 — PowerShell | No. Same LASTEXITCODE/`$?` wrapper and immediate readiness route. | Start isolated server with PowerShell available; use actual `pwsh`/`powershell` terminal. On Windows the manager selects PowerShell rather than consulting Unix `SHELL`; Linux can use installed pwsh. | Diff/source inspection; existing focused test covers immediate PowerShell submission. No new capture required. | not affected |
| U3 — cmd / unsupported observed shell | No. Observed cmd closes idle terminal and reports unsupported completion; no fish wrapper. | Requires Windows with actual cmd fallback (PowerShell unavailable); cannot be produced merely by setting `SHELL=cmd` on this Linux host. | Diff/source inspection; no new capture required. | not affected |
| U4 — Unobserved/manual command | No. Without `observeCompletion`, original command is typed directly. A manual action from the client is not evidence for the changed wrapper. | Run a saved action manually in a terminal. Explicit runner-without-observation is a service fixture if needed; cannot force that internal option through a normal tracked setup UI. | Diff/source inspection; no new capture required. | not affected |
| U5 — Skip/startup/lifecycle controls | No. No matching action, no worktree, fenced-link origin, missing project/settings, terminal-open failure, exited/null shell, startup prompt/quiet readiness, cancellation, `stopOnSettle`, and output filtering do not acquire new behavior. Settings overrides and first matching action still select the command before wrapping. | Omit the action/worktree or use an appropriately fenced caller; shell-exit/rc prompt cases require a controlled shell fixture; resolution and transport failures use service-level fixtures. Ordinary `t3.json` cannot force every internal failure. | Existing readiness/listener tests and source inspection; no cross-product of unchanged control branches with F1–F7 is necessary. Zero/nonzero completion handling is already represented by F3/F4. | not affected |

Successful real-app command from the PR transcript:

```fish
echo "setup ok in $T3CODE_WORKTREE_PATH"
set -l n 0
for i in 1 2 3; set n (math $n + $i); end; echo "sum $n" # trailing comment
```

The minimum set is the F1 before/after stills with elapsed-time/missing-marker context, F2 after still plus its filesystem observation, F3 successful real-app transcript, and the PTY table for valid encoding, nonzero/exit, and stdin boundaries. Repeated app captures for numeric statuses, setup async variants, settle routing, other clients, or unchanged shell branches would not add a distinct changed expression to this server-only proof.

All three PNGs were downloaded from the PR body's relative attachment URLs, resolved against `https://git.cafe`, and opened at their original 1280×800 resolution:

| Capture | Alt-text check and image hygiene |
|---|---|
| [Before](https://git.cafe/api/attachments/attach_01m6vp0hwzfaktww0vzz078eag) | Confirms Setting up worktree, Setup 2m 17s, fish parse error, and no printed completion result. Sentinel text in echoed input is not an emitted result. Setup/sidebar spinners and stop control are expected for this pending row. The low terminal viewport includes a redisplayed command, consistent with the reported interactive-fish failure. |
| [After: issue command](https://git.cafe/api/attachments/attach_01m6vp0jw1faktww101rs0q7w0) | Confirms Failed, “Setup script exited with 1”, Retry, disabled/unstarted agent stage, source wrapper/error, marker `:1`, and returned fish prompt. The toast/scroll position hides the setup duration and top of the stage, so “544 ms” is not visually verified. It does not hide the required failure/Retry/terminal evidence. |
| [After: two-line command](https://git.cafe/api/attachments/attach_01m6vp0kesfaktww1arw1r8xx3) | Confirms malformed first line plus valid redirected second line in submitted body, parse failure, marker `:1`, and Failed/Retry. No visible second-line output is expected because it redirects to a file; rely on the separately reported absence check for that claim. Toast/top-of-card obstruction has the same limit as the other after image. |

All three model-picker regions are visibly redacted with opaque rectangles; no runtime model identifiers are visible. No ghosting or unexplained loading spinner is present in the completed failure rows. Remaining viewport cropping/wrapping and the raw control-sequence fragments in the small setup tail do not hide the required terminal/error state and are not a new render change. The captures are suitable as state/transcript evidence, not clean full-card layout or latency evidence.

Before — main, still waiting:

![Before: main setup still waiting at 2m 17s, with fish parse error in the terminal](https://git.cafe/api/attachments/attach_01m6vp0hwzfaktww0vzz078eag)

After — issue command fails with Retry and marker 1:

![After: setup failure with Retry and terminal completion marker 1](https://git.cafe/api/attachments/attach_01m6vp0jw1faktww101rs0q7w0)

After — malformed multiline body:

![After: multiline parse failure with Retry and terminal completion marker 1](https://git.cafe/api/attachments/attach_01m6vp0kesfaktww1arw1r8xx3)

Independent verification during this audit: `vp test run apps/server/src/project/ProjectSetupScriptRunner.test.ts` passed 9/9 on the reviewed head. The new regression test checks that the malformed command and completion-report text share the fish submission; it does not execute fish or prove emitted status by itself. The historical PTY table and real-app evidence supply that behavior proof. GitCafe reported 34/34 checks successful at this head; existing bot reviews were made on the capture commit and are not presented as new-head re-reviews. macOS and fish versions older than 3.7 were not covered by the historical runs. No new real-app or real-fish session was run in this read-only proof audit.

Only scratch files and the requested public matrix upload were produced. The test process exited normally and no audit-started server or long-lived process remains. The review worktree stayed clean. Disk availability was 109 GiB at entry, above the 5 GiB floor.

AI-assisted; verified with the tests and tools listed above.

FRICTION: none

VERDICT: SUFFICIENT
