All runtime probes done, both suites green. Consolidating the verdict.

## Verdict
**VERDICT: DO-NOT-SHIP (7 findings)** - worst: gemini's `comm_name` (`node-MainThread`) matches *every* Node process on the machine, so any `npm`/`node` command in a JupyterLab terminal is claimed as a running Gemini session, mis-tinting unrelated terminals and writing misattributed entries into the gemini colour store.

## Findings

1. **[MAJOR] Gemini comm matches every Node process - terminal identity, tint and colour write-back contaminated** - `providers/gemini.py:449` (`comm_name = "node-MainThread"`), consumed by `core/routes.py:134-148` (`_tree_assistant`) and `src/core/terminals.ts:301-337`. **FACT, runtime-tested**: a plain `node -e ...` on this machine reports `/proc/<pid>/comm` = `node-MainThread` - identical to a real `gemini` process (both tested). Failure scenario: user opens a JupyterLab terminal in a project registered in gemini's `projects.json` and runs `jlpm watch` / `npm run dev` (any node process). Gemini's 30s colour loop probes it, `TerminalHandler` answers `running: true` (comm matched, session id null), `sessionForCwds` (`colour.ts:101`) maps the cwd onto the project's gemini row → (a) the unrelated terminal's tab is tinted with the gemini conversation's colour, and (b) `terminals.ts:311-320` write-back: a colour the user set by hand on that npm tab is **persisted server-side as that gemini conversation's colour** (`colourSource !== 'native'` gate passes). Secondary effect: a node process sitting *above* another assistant in a terminal's tree makes BFS claim the tree for gemini and answer `running: false` to the true provider, costing reuse. The gemini.py:443-448 comment acknowledges only the reuse-ladder half, not the colour loop. Fix: gemini's identity must not rest on comm alone - after a comm match, confirm from `/proc/<pid>/cmdline` that the script being run is actually the gemini bundle (e.g. a `confirm_pid(pid)` store hook checked in `_tree_assistant`, or gemini's `session_id_for_pid` gating `running`) before answering `running: true`.

2. **[MINOR] New-session pin clearing never fires for kimi/codex - row stays stuck on the old switch target** - `core/routes.py:696-700` clears the pin only `if new_session_id:`, but kimi and codex declare `mintsNewSessionId: false` (`src/providers/kimi.ts`, `codex.ts`), so `_newSession` (`panel.ts:737-767`) sends no `new_session_id` and the guard never fires. Failure scenario: kimi project, user switches to old conversation B (pin written), later clicks "New Kimi Session" → new conversation created by the CLI, but the pin still names B, and the pin beats recency (`kimi.py:330-347`) - the row keeps showing B, and clicking it resumes B, not the conversation the user just started. Contradicts the stated intent at routes.py:697 ("a new conversation supersedes a prior switch"). Fix: clear the pin on any launch that is a new session (`session_id is None and fork_from is None`), not only on a minted-id one.

3. **[MINOR] Colour entries dropped for conversations whose deletion failed** - `core/routes.py:342` builds `forget` from *all requested* ids, and `colour_store.drop_colours` runs at `:364` regardless of per-id outcome; the frontend duplicates this (`panel.ts:651`, `panel.ts:1039`). Failure scenario: multi-select delete of 3 codex threads, `codex delete --force` refuses one (`codex.py:588-599` logs and skips it) → the surviving conversation's user-set tab colour is deleted from the store; its terminal loses its tint on the next reconcile. Fix: have `delete_branches` report *which* ids were removed (it already knows) and drop colours for exactly those.

4. **[MINOR] Migration marker committed before the settings half lands - mapped settings lost on a first-activation settings failure** - `core/migrate.py:116-122` marks a provider migrated in the same POST that *returns* the keys, but the keys are applied client-side at `src/index.ts:291-294` via `settings?.set` - a no-op when `settingRegistry.load` failed (`index.ts:323-330` sets `settings = null`) and migrate still runs (`index.ts:333`). Failure scenario: fresh machine, first activation, transient settings-registry error → favourites migrate fine (server-side), but a user's carried-over `dangerouslySkipPermissions: true` is silently dropped forever - the marker blocks every retry. Safe-direction loss (unsafe modes revert to off), hence MINOR. Fix: skip the migrate call (or skip marking) when `settings` is null, retrying next activation.

5. **[MINOR] Trash failure silently escalates to permanent deletion after the dialog promised "moved to trash"** - `core/store.py:118-125`: any `send2trash` exception falls through to `shutil.rmtree`/`unlink`. **FACT, tested**: in this environment `send2trash` raises `TrashPermissionError` for files on `/tmp` (no writable `/.Trash-1000`) while succeeding under `$HOME`. Failure scenario: `CLAUDE_CONFIG_DIR`/`KIMI_CODE_HOME` pointed at a mount where trashing fails (WSL2 `/mnt/c`, NFS) → user confirms a dialog that explicitly says "moved to trash" (`panel.ts:546`) → data is permanently destroyed with no warning. Fix: when `to_trash` was requested and the trash move fails, fail the operation (or at least report the escalation), rather than deleting permanently.

6. **[MINOR] Claude listing stat race → whole poll 500s** - `providers/claude.py:577` (`jsonls.sort(key=lambda p: p.stat().st_mtime ...)`), also `:608`, `:717`, `:783`: a transcript deleted between `glob` and `stat` (concurrent delete from a second browser tab, or Claude's own housekeeping) raises `FileNotFoundError`, which nothing catches - the executor propagates it and `GET providers/claude/sessions` answers 500; the panel shows the inline error strip for a poll cycle. Self-heals on the next poll, but kimi (`kimi.py:325-327`) and gemini (`gemini.py:517-528`) already guard the same stat - claude should too. Fix: wrap the per-file stat in the same OSError-tolerant pattern.

7. **[MINOR] Claude fork colour inheritance calls a route the server categorically refuses** - `panel.ts:835` (`_branchByNativeFlag` → `_colours.inherit`) POSTs `providers/claude/colours`, which always answers 400 `colour_owned_by_assistant` for a `native` provider (`core/routes.py:484-486`). The optimistic cache write in `colour.ts:171` means the fork shows the parent tint until the next `load()` reconciles and drops it - a flicker plus one guaranteed-failing request per fork. **SUSPICION** riding on it: whether a `--fork-session` transcript actually carries the parent's `agent-color` record (the only remaining inheritance path for claude) is unverified - settling test: fork a live claude conversation and grep the new `.jsonl` for `agent-color`. If it does not, claude branches violate the "branches always inherit parent colour" criterion outright. Fix: gate the client-side inherit on `colourSource !== 'native'`.

## Tested and cleared
- **`node-MainThread` claim itself is accurate** - both plain `node` (v26.6.0) and `gemini --help` report exactly that comm; the descriptor comment is right about gemini, wrong only about exclusivity (finding 1)
- **claude/codex/kimi comm names** - all three are native ELF binaries on this machine (`file` output), so their comm equals the binary name as declared; no gemini-style collision
- **SIGWINCH trampoline** (`routes.py:44-61`) - executed in isolation without a tty: syntax valid, the 5s fallback fires (measured 5.18s), `clear` failure is non-fatal, `exec "$@"` runs the argv; no `set -e`/quoting hazards found
- **DEF-1/2/3/5/8/9 closures hold** - 127 pytest + 59 Jest green, including the specific closure tests (`test_the_gate_reads_the_key_the_schema_declares`, `test_gemini_launch_argv_resumes_by_chat_file_never_by_resume`, fork-from contract tests); flat dotted settings keys confirmed against a real saved settings file on disk
- **Migration source shapes match reality** - live `~/.jupyter/lab/user-settings/jupyterlab_claude_code_extension/plugin.jupyterlab-settings` is flat-keyed and `~/.claude/jupyterlab_claude_code_extension.json` carries `favourites` as a string list, exactly what `migrate.py` reads
- **`include_bg=1` vs `include_extras`** - the panel's `include_bg` param (`panel.ts:1056`) is dead, but claude's `branchQuery` hook sends the `include_extras=1` the server actually reads, so bg markers do flow; vestigial only
- **send2trash on `$HOME`** - works, lands in `~/.local/share/Trash` (finding 5 fires only on foreign filesystems)
- **Path-traversal gates** - every `encoded_path`/`session_id` join re-validated at point of use (claude `_project_dir` resolve+relative_to, kimi `_wd_dir_path`, gemini `_chats_dir_path`, codex UUID gate before argv); pin values charset-gated before any join; `test_store_isolation` passes
- **Interrupt safety** - state/colour/marker writes are tmp+fsync+`os.replace`; kimi and gemini forks roll back on failure; torn trailing index/chat lines are tolerated by every reader
- **Declared-but-never-sent fields** (`IRemoveResponse.removed`, `IForkResponse.forked_from`, `IDisposalReport.failed_count` for codex) - traced: no caller reads them, cosmetic interface debt only

Journey check: fresh machine (no stores, no settings) degrades to empty listings everywhere I traced; restart re-reads disk state correctly; interrupt leaves only tolerated torn lines. The one journey step I could not verify at runtime is a live claude fork's transcript contents (finding 7's suspicion) - it needs an interactive claude session.

Not logging to journal: review-only engagement, no artefacts changed.
