`reattachKnownPtys` treats every non-terminated lease as live and calls
`persistPtyBinding`, which had no way to say "bind only". Two of its branches
then rebuild UI the user is not asking for:
- `pty-binding-persistence.ts:133-143` -- `if (args.incarnationId)`
unconditionally deletes the pane's close tombstone.
- `:145-160` -- on a tab-not-found it mints one via
`createMinimalPersistedTerminalTab`. The in-code comment names its only
intended caller: "pty:spawn can beat the debounced writer." Spawn. Reattach
took the same branch.
This is the mechanism canceled ticket STA-4268 described: "Leases have no pane
incarnation and upsert only by target/PTY. Every nonterminal lease is reattached;
frozen coordinates are passed to persistPtyBinding, which creates and flushes
missing tabs and layout leaves." It was fixed in #13326, reverted by #14361,
re-applied by #14384, and reverted again by #14395 (opened and merged nine
seconds apart, empty commit body). `grep -rn "mayCreate" src/` returns nothing on
main -- the mechanism is genuinely out of the tree.
Four parts:
1. `mayCreate` (default true). When false, one pre-mutation check mirrors all
four creating branches and returns `false` without mutating, so a refusal
leaves nothing half-written.
2. The authority gate -- the part both prior attempts lacked, and probably why
both were reverted. `mayCreate: false` alone refuses in two situations: "the
user closed it" AND "the renderer has not published its layout yet". The
second is routine on disconnect->reconnect and is almost certainly the #14361
tab-loss mechanism. The fence was not wrong; it was UNCONDITIONED. It is now
passed only when `hasHostAuthoritativeTerminalMembership()` says the persisted
membership speaks for this worktree, reusing the function already guarding the
same question at `orca-runtime.ts:8608`. Losing a tab is worse than keeping a
duplicate, so an unauthoritative session still gets the creating write.
Authority is read from `local` because that is the partition the write lands
in -- it is local's absence being interpreted. But a pane the `ssh:<target>`
partition still holds is not gone, so it keeps its creating write; refusing
there would strand a live pane behind a binding reattach can no longer reach.
(SSH spawns bind into `ssh:<target>` while this reattach binds into `local` --
GH #12721/#12723, STA-3980. This does not fix that split; it refuses to judge
from one side of it.)
3. `findTerminalTabIdForLeaf` -- bind resolves the tab from the live layout
instead of the lease's frozen `tabId`. Only the leaf half of a pane key is
remint-stable: `detachTerminalPaneToTab` moves a live pane into a new tab, so
a stored tabId names the tab the pane left. Identity vs location.
4. Pane-keyed supersession -- retires siblings on `(targetId, worktreeId,
leafId)` to `expired`, guarded by the durable binding, which reads `local`
then `ssh:<target>` so it is correct whichever partition the binding landed
in. `upsertSshRemotePtyLease` matched `(targetId, ptyId)` alone
(`ssh-pty-lease-operations.ts:34-36`), so a new relay pty id on reattach minted
a SECOND lease instead of updating the first, leaving the predecessor
non-terminated with nothing to retire it.
On refusal the lease goes `expired`, never `terminated` -- `expired` records that
this shell has no surface to reach it through; `terminated` would assert an exit
nothing here observed, which `docs/reference/ssh-execution-boundary.md` forbids.
The remote process is left running.
Deliberately NOT done:
- A collision guard for `upsertSshRemotePtyLease`. Built, tested, and REMOVED --
its own test passed with the guard disabled, i.e. vacuous. Telling "same lease"
from "recycled id on a different shell" needs a relay-start identity, which
would be a twelfth per-tab identity concept; the codebase already carries
eleven (784 refs) that SSH-v3 Phase 3 deletes. Left as an in-code NOTE. This
handles lease DIVERGENCE, not COLLISION.
- A port of #13324. Its own authors deleted its load fold and reverted its
local-only reader in #13326 ("a headless-owned pane still gets a vote before
its lease is retired"); porting it ships a state-destroying migration they
removed.
- `bindPaneShell` from #13325. Its purpose is making `isSupersededPtyId` live,
and that fence does not exist in main. It would have been a refactor plus a
silently-ignored `mayCreate` -- TS drops excess props through spreads
(verified), which is why `mayCreate` is passed as a conditional spread here.
Honesty about scope: this does NOT close the daily-tab-growth report. The
deterministic e2e repro (`ssh-lost-kill-tab-resurrection.spec.ts`, later in this
stack) is byte-identical before and after, and instrumentation shows why -- in
that scenario `restoreReattachedPtyRuntime` is never called at all
(`CREATING TAB` 9 hits, `reattach gate` 0, `BYPASS` 0). The fence is on a path
that bug does not take. It is a real, separately-provable defect; it is not the
headline fix, and must not be claimed as one.
Evidence, A/B on this tree. Disabling the authority gate (`mayCreate = true`) and
the supersession call by hand: **8 of 12 fail**, including "does not resurrect a
tab whose closing pty.kill failed with a transport error" and "holds the live
lease count flat across ten reconnects of one pane"
(`[ 'pty-0', 'pty-1', 'pty-2', …(7) ]` vs `[ 'pty-9' ]`). The 4 that pass both
ways are the over-refusal tripwires, which is the point of having them. Restored:
12 passed; 2,439 passed across `src/main/ssh`, `src/main/persistence` and
`src/main/runtime/workspace-session`.