---
created: 2026-04-20T09:50:00.000Z
completed: 2026-04-20T09:55:00.000Z
title: Fix perform_wrapper_handoff stale pid in psyche-wrapper pidfile
area: psyche
resolution: Shipped as secondary hardening in the SessionEnd cleanup-session fix — `perform_wrapper_handoff` now writes the new child pid to `.psyche-wrapper-<self_id>.pid` after successful spawn. See `.planning/debug/resolved/todlando-psyche-wrapper-died-post-handoff.md`.
files:
  - src/live/wrapper/mod.rs:419-476
  - C:/Users/decid/AppData/Local/spt/status/.psyche-wrapper-{self_id}.pid
---

## Problem

`perform_wrapper_handoff` (src/live/wrapper/mod.rs:419-476) does NOT update `%LOCALAPPDATA%\spt\status\.psyche-wrapper-<self_id>.pid` with the new child wrapper's pid after a successful handoff. Old process exits cleanly, new process rehydrates — but the pidfile still contains the pre-handoff pid. Direct-kill teardown paths (`live stop <id>`, any tool that TerminateProcess's the pid from this file) will miss the live wrapper and target a stale pid (which may be dead or, worse, re-used by an unrelated OS process).

Evidence surfaced during `.planning/debug/todlando-psyche-wrapper-died-post-handoff.md` investigation: doyle-psyche's pidfile still contained pid 44840 (pre-handoff), long after a v1.8.9→v1.8.10 handoff produced a new live wrapper pid. Teardown still works via `soft_stop_perch`'s ready-file removal as an indirect trigger, but the direct-kill path is broken for post-handoff wrappers.

## Solution

TBD — two reasonable shapes:

1. **New wrapper writes pidfile on rehydrate.** In the `handoff_hydrated` branch (where wrapper-state.json was just loaded), immediately overwrite `.psyche-wrapper-<self_id>.pid` with `std::process::id()` before entering the main poll loop. One extra file write at rehydrate boundary; zero runtime cost afterward.

2. **Old wrapper writes new pid before exec.** Inside `perform_wrapper_handoff` after spawn succeeds and the child pid is known, write the child pid into the pidfile before the parent exits. Requires the parent to know the child pid pre-detach (spawn_detached_no_inherit on Windows returns pid; Unix fork returns pid).

Shape (1) is simpler — single code path, no cross-process handoff-time write race. Prefer (1) unless a compelling reason surfaces.

Also consider: log the pidfile rewrite so `$LIVE list-psyches` post-handoff state is auditable, and add a cleanup step so the file is removed on clean wrapper exit (wrapper's `cleanup()`).
