jackwener/OpenCLI · error · CommandFailure

bound_window_operation_blocked

bound_window_operation_blocked

Error message

Session "${existing.session}" is bound to a user tab and does not own an OpenCLI tab lease.

What it means

getAutomationWindow looks up the lease's automation session; if the session exists but is only bound to a user tab (existing.owned === false), the session does not own an OpenCLI-managed window/tab group, so window-level operations are refused with CommandFailure code 'bound_window_operation_blocked'. The library distinguishes owned automation windows from user-bound tabs and only allows window operations on the former.

Source

Thrown at extension/src/background.ts:1129

    kind: 'owned',
    windowId: sessionWindowId,
    owned: true,
    preferredTabId: tabId,
  });
  resetWindowIdleTimer(leaseKey);
  return { tabId, tab };
}

/** Get or create the dedicated automation container window.
 *  This compatibility helper returns the shared owned container. Leases
 *  lease tabs inside it instead of owning separate windows.
 */
async function getAutomationWindow(leaseKey: string, initialUrl?: string): Promise<number> {
  // Check if our window is still alive.
  const existing = automationSessions.get(leaseKey);
  if (existing) {
    if (!existing.owned) {
      throw new CommandFailure(
        'bound_window_operation_blocked',
        `Session "${existing.session}" is bound to a user tab and does not own an OpenCLI tab lease.`,
        'Use page commands on the bound tab, or unbind the session first.',
      );
    }
    try {
      const tabId = existing.preferredTabId;
      if (tabId !== null) {
        const tab = await chrome.tabs.get(tabId);
        if (isDebuggableUrl(tab.url)) return tab.windowId;
      }
      await chrome.windows.get(existing.windowId);
      return existing.windowId;
    } catch {
      // Tab/window was closed by user
      await removeLeaseSession(leaseKey);
    }
  }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Use page-level commands on the bound tab instead of window-level commands
  2. Unbind the session (unbind command) and re-open it as an owned automation window
  3. Create a new named session via the normal open flow so it owns its own window/tab group

Example fix

// before
await runCommand('mysession', 'window resize', { width: 1280 });
// after
const mode = await getSessionMode('mysession'); // 'owned' | 'bound'
if (mode === 'bound') await runCommand('mysession', 'unbind');
await runCommand('mysession', 'window resize', { width: 1280 });
Defensive patterns

Strategy: try-catch

Validate before calling

// before window-level commands, check ownership
const owned = await isOwnedAutomationSession(sessionName); // e.g. via status command
if (!owned) console.warn('Session is bound to a user tab; window commands will be blocked');

Try / catch

try {
  await windowCommand(session);
} catch (e) {
  if (e.code === 'bound_window_operation_blocked') {
    console.error('Use page commands on the bound tab, or unbind and reopen as an owned session');
  } else throw e;
}

Prevention

When it happens

Trigger: Running a window-level automation command (resize, focus, snapshot, etc.) against a session that was previously bound to a regular user tab rather than created as an owned automation window — getAutomationWindow detects existing && !existing.owned and throws.

Common situations: Attaching the automation session to a manually opened browser tab, then trying window commands on it; a lease got downgraded from owned to bound after its group/window was dissolved; scripts assuming every session owns a window.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/57d9b0dbe0a98032. Report an issue: GitHub.