fix(terminal): guard the paste pane key and document the evidence policy

Readiness review follow-ups, none behaviour-changing for reachable inputs.

makePaneKey throws on a malformed leaf/tab id. It is unreachable today (pane.leafId
is a minted UUID and the same pair is already called unguarded from a hotter site),
but the failure mode was bad: the throw escapes before the paste helper's catch is
attached, so the paste would be a silent no-op with no error surface. Degrade to the
pre-fix path instead, with a test.

Also record two things a future reader needs: the process-confirmed branch is dead
for remote-runtime and SSH panes because foreground tracking is disabled there, so a
remote pane's status row is its only evidence; and why this resolver deliberately
omits the shellForeground/routingRevoked/routingTrusted gates its two siblings
enforce - they route input bytes, this only wraps a paste whose payload is
ESC-sanitized downstream.

Refs STA-4294
This commit is contained in:
Brennan Benson
2026-08-13 23:28:33 -07:00
parent 7f2a1dc517
commit 7a9270dea3
2 changed files with 30 additions and 1 deletions
@@ -110,6 +110,16 @@ describe('shouldForceBracketedMultilinePasteForPane', () => {
).toBe(true)
})
it('degrades to the pre-fix path instead of throwing on a malformed pane key', () => {
// Why: makePaneKey throws on a non-UUID leaf or a tabId containing ':'. The throw
// would escape before the paste helper's catch is attached, making the paste a
// silent no-op with no error surface.
expect(() => decide({ leafId: 'not-a-uuid' })).not.toThrow()
expect(decide({ leafId: 'not-a-uuid' })).toBe(false)
expect(() => decide({ tabId: 'tab:with:colons' })).not.toThrow()
expect(decide({ tabId: 'tab:with:colons' })).toBe(false)
})
it('still brackets when shellForeground is latched true while an agent owns the pane', () => {
// Why: shellForeground is republished only at OSC 133 boundaries, so a shell with
// no 133 integration leaves it true while an agent runs. Measured live: vetoing on
@@ -21,6 +21,13 @@ import type { PaneForegroundAgentEntry } from '../../store/slices/pane-foregroun
* in transit (ConPTY, replay) — never an opt-out. Keep this narrow for that reason.
* Bracketing a program that never negotiated is measurably worse than useless: the
* markers arrive as literal payload bytes and ICRNL still turns the CR into a submit.
*
* Why this diverges from terminal-ctrl-enter / terminal-windows-shift-enter, which veto
* on shellForeground/routingRevoked and require routingTrusted: those resolvers decide
* where to ROUTE input bytes, so a forged identity misdelivers keystrokes. This one only
* decides whether to WRAP a paste, and the payload is ESC-sanitized downstream, so the
* worst a forged identity buys is a literal ESC[200~ printed at a program. Do not
* "align" this with those gates — measured live, that reinstates the submit bug.
*/
export function shouldForceBracketedMultilinePasteForPane({
isWindowsClient,
@@ -38,8 +45,20 @@ export function shouldForceBracketedMultilinePasteForPane({
if (isWindowsClient) {
return true
}
const paneKey = makePaneKey(tabId, leafId)
let paneKey: string
try {
paneKey = makePaneKey(tabId, leafId)
} catch {
// Why: unreachable today (pane.leafId is a minted UUID), but a legacy/malformed
// layout must degrade to the pre-fix path, not throw out of the paste handler --
// the throw would escape before pasteTerminalClipboard's catch is attached and
// the paste would be a silent no-op with no error surface.
return false
}
// Why: a process-table confirmed agent is the strongest evidence there is.
// Note this branch is dead for remote-runtime and SSH panes: isForegroundTrackingAllowed
// (pty-connection) returns false for them, so paneForegroundAgentByPaneKey stays empty
// and the status row below is the only evidence a remote pane ever has.
if (isTuiAgent(paneForegroundAgentByPaneKey[paneKey]?.agent)) {
return true
}