stablyai/orca · error · Error

Terminal input is locked

Error message

Terminal input is locked

What it means

Same reader as 386 but on the notes-send path: `terminal.send` succeeded (ok:true) yet result.send.accepted === false. The host accepted the envelope but refused the keystrokes because the terminal input is owned/locked.

Source

Thrown at mobile/src/session/use-mobile-diff-review-send-actions.ts:92

    async (terminal: string, comments: readonly DiffComment[]) => {
      if (!client || connState !== 'connected') {
        throw new Error('Waiting for desktop...')
      }
      // Marked by terminal handle, not by surface, so a paste orphaned here by native
      // chat would ride along with these notes (#10228). Diff review carries no device token.
      if (!(await healMobileNativeChatStaleInput({ client, terminal, deviceToken: null }))) {
        throw new Error('Failed to send notes')
      }
      const response = await client.sendRequest('terminal.send', {
        terminal,
        text: formatMobileDiffReviewPrompt(comments),
        enter: true
      })
      if (!response.ok) {
        throw new Error(response.error?.message || 'Failed to send notes')
      }
      if (!readMobileReviewTerminalSendAccepted(response.result)) {
        throw new Error('Terminal input is locked')
      }
      await markNotesSent(comments)
      triggerSuccess()
      setActionError('Review notes sent')
      setSendSheet(null)
    },
    [client, connState, markNotesSent, setActionError, setSendSheet]
  )

  const createTerminalAndSend = useCallback(
    async (comments: readonly DiffComment[]) => {
      if (!client || connState !== 'connected') {
        throw new Error('Waiting for desktop...')
      }
      const response = await client.sendRequest('session.tabs.createTerminal', {
        worktree: `id:${worktreeId}`,
        activate: false,
        select: true,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Close competing native chat on that terminal.
  2. Retry the send.
  3. Pick a different terminal / create a fresh one.
  4. Heal stale input again.

Example fix

// before
if (!readMobileReviewTerminalSendAccepted(response.result)) {
  throw new Error('Terminal input is locked')
}
// after - fall back to a fresh terminal
if (!readMobileReviewTerminalSendAccepted(response.result)) {
  return createTerminalAndSend(comments)
}
Defensive patterns

Strategy: retry

Validate before calling

if (isMobileNativeChatInputStale(terminal)) {
  await healMobileNativeChatStaleInput({ client, terminal, deviceToken: null })
}

Type guard

function readMobileReviewTerminalSendAccepted(value: unknown): boolean {
  if (!isRecord(value) || !isRecord(value.send)) return true
  return value.send.accepted !== false
}

Try / catch

try {
  await sendPromptToTerminal(terminal, comments)
} catch (err) {
  if (/locked/.test(err instanceof Error ? err.message : '')) {
    return createTerminalAndSend(comments)
  }
  throw err
}

Prevention

When it happens

Trigger: A native chat composer or another surface owns the terminal input line; an overlay has focus; the stale-input heal consumed the marker but the lock returned.

Common situations: Native chat left the line owned; two send surfaces target the same terminal; host lock state stale.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/ee77d904a4ad33af. Report an issue: GitHub.