fix(orchestration): stop the sender-terminal refusal recommending another pane's handle

The structured-session guard told callers to pass `--from <terminal-handle>`, but the
explicit-flag branch returns before that guard runs — so following the advice succeeds,
against a handle that necessarily belongs to a different pane, and the next `check`
consumes that pane's unread mail.

Both refusals now say what is actually true: no handle names a structured chat session,
and a caller that does have one should pass its own.

Also pins ORCA_STRUCTURED_SESSION in the gate CLI test, which until now decided which
refusal it exercised from ambient environment.
This commit is contained in:
Brennan Benson
2026-09-16 15:11:13 -07:00
parent 85d1ffc072
commit ce4f4da809
2 changed files with 14 additions and 4 deletions
@@ -34,6 +34,9 @@ import { okFixture, queueFixtures } from '../test-fixtures'
const originalTerminalHandle = process.env.ORCA_TERMINAL_HANDLE
const originalPaneKey = process.env.ORCA_PANE_KEY
// Why: a structured-session marker inherited from the runner diverts these cases to the
// structured refusal, so which branch they exercise would depend on who ran them.
const originalStructuredSession = process.env.ORCA_STRUCTURED_SESSION
const restoreEnv = (name: string, value: string | undefined): void => {
if (value === undefined) {
@@ -54,6 +57,7 @@ describe('orchestration gate commands carry caller identity', () => {
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
delete process.env.ORCA_TERMINAL_HANDLE
delete process.env.ORCA_PANE_KEY
delete process.env.ORCA_STRUCTURED_SESSION
process.exitCode = 0
})
@@ -62,6 +66,7 @@ describe('orchestration gate commands carry caller identity', () => {
errorSpy.mockRestore()
restoreEnv('ORCA_TERMINAL_HANDLE', originalTerminalHandle)
restoreEnv('ORCA_PANE_KEY', originalPaneKey)
restoreEnv('ORCA_STRUCTURED_SESSION', originalStructuredSession)
process.exitCode = 0
})
@@ -192,7 +197,7 @@ describe('orchestration gate commands carry caller identity', () => {
expect(process.exitCode).toBe(1)
const stderr = errorSpy.mock.calls.map((call) => String(call[0])).join('\n')
expect(stderr).toContain('Pass --from <terminal-handle>')
expect(stderr).toContain("Pass --from with your own terminal's handle")
expect(callMock).not.toHaveBeenCalledWith('orchestration.gateCreate', expect.anything())
})
@@ -36,10 +36,14 @@ export async function resolveOrchestrationTerminalHandle(
// rightful worker never saw its mail. Refusing is the only honest answer: this child genuinely
// cannot infer its own identity.
if (isStructuredSessionWithoutIdentity()) {
// Why not suggest --${flagName}: the explicit-flag branch above returns before this guard, so
// that advice succeeds — against a handle that necessarily belongs to another pane.
throw new RuntimeClientError(
'no_active_sender_terminal',
`This chat session has no orchestration identity of its own, so --${flagName} cannot be inferred. ` +
`Pass --${flagName} <terminal-handle> explicitly; guessing would act on another pane's mailbox.`
`This chat session has no orchestration identity of its own, so --${flagName} cannot be inferred, ` +
`and no terminal handle names it — every live handle belongs to a different pane, whose mailbox ` +
`passing it would consume. Drive a worker directly instead: create a worktree, create a terminal ` +
`in it with an agent, then use terminal send and terminal read.`
)
}
if (flagName === 'from') {
@@ -192,6 +196,7 @@ export function throwNoActiveSenderTerminal(): never {
throw new RuntimeClientError(
'no_active_sender_terminal',
'Could not determine the sender terminal for this orchestration command. ' +
'Pass --from <terminal-handle> or run the command inside a live Orca terminal with ORCA_TERMINAL_HANDLE set.'
"Pass --from with your own terminal's handle — another pane's handle would act on its mailbox — " +
'or run the command inside a live Orca terminal with ORCA_TERMINAL_HANDLE set.'
)
}