Hmbown/CodeWhale · error · ExecError

display index is out of range

Error message

display index is out of range

What it means

switch_display validates the requested 1-based display index against the list produced by list_displays (Screen.AllScreens). It throws this ExecError when no display has that index, so activeDisplay is never set to a nonexistent monitor.

Solutions

  1. Call list_displays() first and pick an index from its returned 1-based indexes
  2. Remember indexes are 1-based; use 1 for the primary/first display
  3. Catch ExecError and fall back to list_displays to re-enumerate after hardware changes

Example fix

// before
await switchDisplay({ index: 2 });
// after
const { displays } = await listDisplays();
await switchDisplay({ index: displays[0].index });
Defensive patterns

Strategy: validation

Validate before calling

async function safeSwitchDisplay(backend, index) {
  const { displays } = await backend.list_displays();
  if (!displays.some(d => d.index === index)) {
    throw new Error(`display ${index} not found; available: ${displays.map(d => d.index).join(",")}`);
  }
  return backend.switch_display({ index });
}

Type guard

const isValidDisplayIndex = (v, displays) =>
  Number.isInteger(v) && v >= 1 && displays.some(d => d.index === v);

Try / catch

try {
  await backend.switch_display({ index: 2 });
} catch (e) {
  if (String(e.message) === "display index is out of range") {
    const { displays } = await backend.list_displays();
    await backend.switch_display({ index: displays[0].index });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling switch_display({index: N}) where N < 1, N > number of connected monitors, or after a monitor was unplugged so the cached index no longer exists.

Common situations: Scripts hard-coding a monitor index from another machine; display hot-unplug; single-monitor machines receiving index 2; confusion between 0-based and 1-based indexing.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/a73d3413f064c459. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:281

    closeSession: async () => { await browser.close().catch(() => {}); },
    probe: async () => {
      const psOk = await ps("Write-Output 'ok'").then((r) => r.code === 0).catch(() => false);
      return {
        platform: "win32",
        powershell: psOk,
        capabilities: { screenshot: psOk, accessibility_tree: psOk, clipboard: psOk, recording: false, raw_input: psOk, held_input: psOk && opts.exec?.persistentInputOwner === true },
        note: "Recording is unavailable until session-owned cleanup is implemented; use screenshots. UIA accessibility works without extra installs. Held keys, held buttons and drag require a connected Computer Use desktop helper.",
      };
    },
    list_displays: async () => {
      const d = await psJson(`Add-Type -AssemblyName System.Windows.Forms;
$arr = @([System.Windows.Forms.Screen]::AllScreens | ForEach-Object { [pscustomobject]@{ name = $_.DeviceName; primary = $_.Primary; x = $_.Bounds.X; y = $_.Bounds.Y; w = $_.Bounds.Width; h = $_.Bounds.Height } });
@{ displays = $arr } | ConvertTo-Json -Depth 4 -Compress;`, { timeoutMs: 15_000 });
      return d.displays.map((x, i) => ({ index: i + 1, name: x.name, points: { x: x.x, y: x.y, w: x.w, h: x.h }, pixels: { w: x.w, h: x.h }, scale: 1, main: !!x.primary }));
    },
    async switch_display({ index = 1 }) {
      const displays = await this.list_displays();
      if (!displays.some(d => d.index === index)) throw new ExecError("display index is out of range");
      activeDisplay = index;
      return { activeDisplay };
    },
    list_apps: async () => {
      const j = await psJson(`Add-Type -AssemblyName System.Windows.Forms;
$out = Get-Process | Where-Object { $_.MainWindowTitle } | ForEach-Object { [pscustomobject]@{ name = $_.ProcessName; pid2 = $_.Id; title = $_.MainWindowTitle } } | ConvertTo-Json -Compress;
if (-not $out) { $out = '[]' }
Write-Output ('{"apps": ' + $out + '}');`);
      return { apps: (Array.isArray(j.apps) ? j.apps : [j.apps]).map((a) => ({ name: a.name, pid: a.pid2, title: a.title })) };
    },
    list_windows: async (args = {}) => {
      if (Object.hasOwn(args, "app_ref") || Object.hasOwn(args, "window_id")) throw unsupportedSelector("Windows list_windows does not support app_ref or window_id; omit them to list all windows");
      const j = await psJson(`$ErrorActionPreference = 'Stop';
Add-Type -AssemblyName System.Windows.Forms;
Add-Type -TypeDefinition @'
using System;
using System.Text;
using System.Collections.Generic;

View on GitHub (pinned to 73e0f67d83)