OpenAI computer_use Tool Configuration (Display, Environment, Model) — Fix Guide (2026)
computer_use · Configuration Severity: High 400

OpenAI computer_use Tool Configuration (Display, Environment, Model)

The computer_use tool ships on Responses API with two configuration levers that trip teams up: environment (browser vs. windows vs. mac vs. ubuntu) and display_width/display_height — the pixel dimensions the model reasons about. Wrong shape = 400; wrong dimensions = nonsense actions that never quite land on real buttons. Here's the setup.

TL;DRTool declaration on Responses: tools=[{"type": "computer_use_preview", "display_width": 1280, "display_height": 800, "environment": "browser"}]. GA models accept {"type": "computer"}. Model must be gpt-5.4 or later. Environment options: browser, windows, mac, ubuntu. Display dimensions must match what you actually render — the model computes click coordinates against them; mismatched actual/declared dims produce off-target clicks.

Real error messages you'll see

BadRequestError — model does not support tool
BadRequestError — model does not support tool
openai.BadRequestError: Error code: 400 - {'error': {'message': "Tool 'computer' is not supported for model 'gpt-4.1'. Use gpt-5.4 or later.", 'type': 'invalid_request_error'}}
# Computer use requires gpt-5.4+ or one of the dedicated computer-use models.
BadRequestError — missing display dimensions
BadRequestError — missing display dimensions
openai.BadRequestError: Error code: 400 - {'error': {'message': "Field 'display_width' is required for tool type 'computer_use_preview'.", 'type': 'invalid_request_error'}}
# Preview shape requires display_width, display_height, environment. GA 'computer' shape has defaults.
Actions land at wrong coordinates
Actions land at wrong coordinates
# Model returned {"type": "click", "x": 640, "y": 400} for a "Login" button.
# Your actual browser is 1920×1080, not the declared 1280×800.
# Click lands at (640, 400) in a 1920px viewport — wrong place.
# Fix: render at the declared dimensions, OR scale coordinates back to your actual viewport.

Tool declaration shape by version

FieldPreview (computer_use_preview)GA (computer)
type"computer_use_preview""computer"
environmentRequired: browser / windows / mac / ubuntuOptional (defaults browser)
display_widthRequiredOptional (defaults ~1280)
display_heightRequiredOptional (defaults ~800)
Model supportcomputer-use-preview, gpt-5.4+gpt-5.4, gpt-5.5, gpt-5.6
Actions shapeSingle action per computer_callBatched actions[] per computer_call

Root causes (ranked by frequency)

Based on OpenAI developer reports; percentages sum to 102%.

  • 24%
    Old model with computer_use tool. gpt-4.1 and earlier don't support computer use — must be gpt-5.4+ or the dedicated computer-use-preview model.
  • 18%
    display_width/height not declared. Preview shape requires them explicitly; missing them 400s.
  • 14%
    Actual browser size ≠ declared dimensions. Model reasons in the declared coordinate space; if your headless browser is 1920×1080 but you declared 1280×800, clicks miss by 33-50%.
  • 12%
    Wrong environment. Declared windows but running headless Chrome on Linux. Model calibrates its UI assumptions to the environment; wrong choice → wrong action patterns.
  • 10%
    Preview vs GA action shape confusion. Consumer expects action (single), but GA model returned actions (batched). Missing the array → dropped actions.
  • 9%
    Missing initial screenshot. First request should include a screenshot in input; without it, model asks for one anyway (wasted round-trip).
  • 8%
    Tool provided without input describing the goal. "Go to Amazon and buy socks" — model needs concrete task text to steer actions, not just a tool declaration.
  • 7%
    No truncation config for long-horizon tasks. Screenshots accumulate; context bloats fast. Set truncation="auto" so old screenshots are trimmed.

How to fix it

Fix #1

Correct tool declaration — preview vs GA, model + dimensions + environment

The 400-proof setup.

For preview: explicitly declare type, environment, display_width, display_height. For GA: type: "computer" is sufficient (defaults fill in). Use gpt-5.4+ models. Match declared display dimensions exactly to what your actual runtime is rendering.

declare_tool.pypython
from openai import OpenAI

client = OpenAI()


# ✅ PREVIEW shape — explicit dimensions and environment required
PREVIEW_TOOL = {
    "type": "computer_use_preview",
    "display_width": 1280,        # matches your headless browser viewport
    "display_height": 800,
    "environment": "browser",     # browser | windows | mac | ubuntu
}


# ✅ GA shape — simpler, defaults apply
GA_TOOL = {"type": "computer"}


# ✅ GA shape with explicit dimensions (still works, overrides defaults)
GA_TOOL_EXPLICIT = {
    "type": "computer",
    "display_width": 1920,
    "display_height": 1080,
    "environment": "browser",
}


# ✅ Initial call with screenshot in input
def start_computer_task(goal: str, initial_screenshot_b64: str, viewport=(1280, 800)):
    tool = {
        "type": "computer_use_preview",       # or "computer" for GA
        "display_width": viewport[0],
        "display_height": viewport[1],
        "environment": "browser",
    }

    resp = client.responses.create(
        model="gpt-5.4",                       # gpt-5.4 or later
        tools=[tool],
        input=[
            {
                "role": "user",
                "content": [
                    {"type": "input_text", "text": goal},
                    {
                        "type": "input_image",
                        "image_url": f"data:image/png;base64,{initial_screenshot_b64}",
                    },
                ],
            }
        ],
        truncation="auto",                     # trim old screenshots automatically
    )
    return resp


# ✅ Environment options and their calibration effects
ENVIRONMENT_HINTS = {
    "browser": {
        "typical_viewport": (1280, 800),
        "input_devices": "mouse + keyboard",
        "notes": "Chrome/Firefox/Edge in browser mode. Model assumes browser controls (URL bar, back button).",
    },
    "windows": {
        "typical_viewport": (1920, 1080),
        "input_devices": "mouse + keyboard",
        "notes": "Windows desktop apps. Model assumes Windows title bars, taskbar, start menu.",
    },
    "mac": {
        "typical_viewport": (1440, 900),
        "input_devices": "mouse + keyboard",
        "notes": "macOS. Cmd instead of Ctrl for shortcuts.",
    },
    "ubuntu": {
        "typical_viewport": (1920, 1080),
        "input_devices": "mouse + keyboard",
        "notes": "Linux desktop. Model assumes typical Ubuntu UI patterns.",
    },
}


# ✅ Common viewport matches
STANDARD_VIEWPORTS = {
    "browser_default":      (1280, 800),
    "browser_hd":           (1920, 1080),
    "browser_mobile":       (390, 844),        # iPhone 14 Pro
    "browser_tablet":       (1024, 768),        # iPad
    "windows_default":      (1920, 1080),
    "mac_retina":           (2560, 1600),
}


# ✅ Runtime-declared match — read actual browser dims and pass to tool
async def start_with_actual_dims(page, goal: str):
    """Playwright example — get actual viewport and pass to tool config."""
    viewport = page.viewport_size or {"width": 1280, "height": 800}

    screenshot = await page.screenshot()
    import base64
    screenshot_b64 = base64.b64encode(screenshot).decode()

    resp = client.responses.create(
        model="gpt-5.4",
        tools=[{
            "type": "computer_use_preview",
            "display_width": viewport["width"],
            "display_height": viewport["height"],
            "environment": "browser",
        }],
        input=[
            {
                "role": "user",
                "content": [
                    {"type": "input_text", "text": goal},
                    {"type": "input_image",
                     "image_url": f"data:image/png;base64,{screenshot_b64}"},
                ],
            }
        ],
        truncation="auto",
    )
    return resp


# ❌ ANTI-PATTERN — declared vs actual dimension mismatch
# Playwright default is 1280×720, but you declared 1920×1080 to the tool.
# Model returns clicks in 1920×1080 space; they land off-target in the actual page.
# Fix: always read the ACTUAL viewport and pass those dims to the tool.


# ❌ ANTI-PATTERN — GA "computer" shape with old model
# resp = client.responses.create(
#     model="gpt-4.1",                    # too old
#     tools=[{"type": "computer"}],
#     ...
# )
# BadRequestError: Tool 'computer' is not supported for model 'gpt-4.1'


# ❌ ANTI-PATTERN — preview shape without dimensions
# resp = client.responses.create(
#     model="gpt-5.4",
#     tools=[{"type": "computer_use_preview"}],   # missing display_width/height/environment
#     ...
# )
# BadRequestError: Field 'display_width' is required
Note: GA {"type": "computer"} is simpler for prototyping. For production, declare dimensions and environment explicitly — makes runtime behavior reproducible and makes bugs easier to diagnose. The GA defaults change over time; explicit config doesn't.
Fix #2

Match declared dimensions to actual runtime — never guess

Fixes off-target clicks and phantom UI misses.

The model computes coordinates in the declared display space. If you tell it "1280×800" but your headless browser is at 1920×1080, every click is off by a scale factor. Solutions: (1) render at the exact dimensions you declare, (2) scale coordinates back before executing, or (3) let the runtime read actual dims and pass those.

dimension_alignment.pypython
from playwright.async_api import async_playwright
import base64


# ✅ APPROACH 1 — force browser to declared dims
async def launch_at_declared_dims(width: int = 1280, height: int = 800):
    """Playwright launched with a specific viewport that matches declared dims."""
    p = await async_playwright().start()
    browser = await p.chromium.launch(headless=True)
    context = await browser.new_context(viewport={"width": width, "height": height})
    page = await context.new_page()
    return p, browser, page


# ✅ APPROACH 2 — read actual dims, pass to tool
async def declare_from_actual(page):
    """Read Playwright's actual viewport, use those for tool declaration."""
    viewport = page.viewport_size or {"width": 1280, "height": 800}
    return {
        "type": "computer_use_preview",
        "display_width": viewport["width"],
        "display_height": viewport["height"],
        "environment": "browser",
    }


# ✅ APPROACH 3 — scale coordinates on execution
class CoordinateScaler:
    """When declared and actual differ (e.g. Retina display), scale actions."""

    def __init__(self, declared: tuple[int, int], actual: tuple[int, int]):
        self.dx = actual[0] / declared[0]
        self.dy = actual[1] / declared[1]

    def scale_action(self, action: dict) -> dict:
        """Rewrite coordinates in a computer_use action to actual dims."""
        scaled = dict(action)
        if "x" in scaled:
            scaled["x"] = int(scaled["x"] * self.dx)
        if "y" in scaled:
            scaled["y"] = int(scaled["y"] * self.dy)

        # Drag actions have path arrays
        if "path" in scaled:
            scaled["path"] = [{"x": int(pt["x"] * self.dx), "y": int(pt["y"] * self.dy)}
                              for pt in scaled["path"]]

        # Scroll deltas can also be dimension-relative
        if "scroll_x" in scaled:
            scaled["scroll_x"] = int(scaled["scroll_x"] * self.dx)
        if "scroll_y" in scaled:
            scaled["scroll_y"] = int(scaled["scroll_y"] * self.dy)

        return scaled


# Example: model reasons in 1280×800 but our screen is 1920×1080
scaler = CoordinateScaler(declared=(1280, 800), actual=(1920, 1080))
scaled = scaler.scale_action({"type": "click", "x": 640, "y": 400, "button": "left"})
# scaled = {"type": "click", "x": 960, "y": 540, "button": "left"}


# ✅ Take screenshot at declared dims (downscale)
async def screenshot_at_declared(page, declared_w: int, declared_h: int) -> bytes:
    """Screenshot the current page, resize to declared dims before sending."""
    raw_bytes = await page.screenshot()
    from PIL import Image
    import io as _io

    img = Image.open(_io.BytesIO(raw_bytes))
    if img.size != (declared_w, declared_h):
        img = img.resize((declared_w, declared_h), Image.LANCZOS)

    out = _io.BytesIO()
    img.save(out, format="PNG")
    return out.getvalue()


# ✅ Mobile viewport support
async def mobile_computer_use():
    """iPhone-sized viewport for testing mobile web flows."""
    p = await async_playwright().start()
    iphone = p.devices["iPhone 14 Pro"]
    browser = await p.chromium.launch(headless=True)
    context = await browser.new_context(**iphone)
    page = await context.new_page()
    await page.goto("https://example.com")

    tool = {
        "type": "computer_use_preview",
        "display_width": iphone["viewport"]["width"],
        "display_height": iphone["viewport"]["height"],
        "environment": "browser",
    }
    return page, tool


# ✅ Verification pattern — after every N actions, screenshot + compare
async def verify_declared_match(page, declared_w: int, declared_h: int):
    """Sanity check: is Playwright still rendering at declared dims?"""
    actual = page.viewport_size
    if not actual or actual["width"] != declared_w or actual["height"] != declared_h:
        raise RuntimeError(
            f"viewport drift: declared={declared_w}×{declared_h}, "
            f"actual={actual['width'] if actual else '?'}×{actual['height'] if actual else '?'}"
        )
Note: Playwright's default viewport is 1280×720 — a common trap when you declared 1280×800 (off by 80px in height). Always read page.viewport_size before declaring, and log any drift. Retina/HiDPI display captures give physical pixels not CSS pixels; downscale before sending.
Fix #3

Task-shaped input — describe the goal, don't just enable the tool

Fixes "model does nothing useful" bugs.

Enabling the computer_use tool without concrete task text produces vague or random actions. Provide (1) an initial screenshot of the starting state, (2) clear goal text ("Find and click the Submit button", not "figure out what to do"), and (3) any constraints ("Don't enter payment info without asking"). Model uses these to steer every action.

shape_the_task.pypython
import base64
from openai import OpenAI

client = OpenAI()


# ❌ ANTI-PATTERN — vague goal, no screenshot
def bad_start():
    return client.responses.create(
        model="gpt-5.4",
        tools=[{"type": "computer_use_preview", "display_width": 1280,
                "display_height": 800, "environment": "browser"}],
        input="Do the thing on the page.",     # too vague
    )


# ✅ GOOD — screenshot + explicit goal + constraints
def good_start(screenshot_b64: str):
    return client.responses.create(
        model="gpt-5.4",
        tools=[{"type": "computer_use_preview", "display_width": 1280,
                "display_height": 800, "environment": "browser"}],
        instructions=(
            "You are a browser automation agent. "
            "Complete the task by clicking, typing, and scrolling as needed. "
            "IMPORTANT: Never enter passwords, credit card numbers, or SSNs "
            "without explicit permission. If you encounter a login screen "
            "you weren't authorized to complete, stop and report back."
        ),
        input=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "input_text",
                        "text": (
                            "Task: Find the pricing page and report back the price "
                            "of the \"Pro\" plan (monthly billing). Do not click any "
                            "\"Buy\" or \"Subscribe\" buttons — just read the price."
                        ),
                    },
                    {
                        "type": "input_image",
                        "image_url": f"data:image/png;base64,{screenshot_b64}",
                    },
                ],
            }
        ],
        truncation="auto",
    )


# ✅ Structured task template
def make_task_input(
    goal: str,
    constraints: list[str],
    success_criteria: str,
    screenshot_b64: str,
    starting_url: str | None = None,
) -> list:
    task_lines = [f"TASK: {goal}"]

    if starting_url:
        task_lines.append(f"STARTING URL: {starting_url}")

    if constraints:
        task_lines.append("CONSTRAINTS:")
        task_lines.extend(f"  - {c}" for c in constraints)

    task_lines.append(f"SUCCESS: {success_criteria}")
    task_lines.append("")
    task_lines.append("Report back when you have the answer, or if you get stuck.")

    return [
        {
            "role": "user",
            "content": [
                {"type": "input_text", "text": "\n".join(task_lines)},
                {"type": "input_image", "image_url": f"data:image/png;base64,{screenshot_b64}"},
            ],
        }
    ]


# ✅ Example use
input_data = make_task_input(
    goal="Extract the top 5 news headlines from the front page",
    constraints=[
        "Don't click on individual articles",
        "Don't click on ads or paywall prompts",
        "Report headlines exactly as displayed",
    ],
    success_criteria="Return a numbered list of 5 headlines and their URLs",
    screenshot_b64="...",
    starting_url="https://example-news-site.com",
)


# ✅ For agents that need to run multiple similar tasks, template the instructions
INSTRUCTIONS_TEMPLATE = """You are a browser automation agent operating in a {environment} environment
at {width}×{height} pixels.

You can:
- click(x, y): click at coordinates
- type(text): type text into focused input
- key(name): press a key (Enter, Tab, Escape, ArrowDown, etc.)
- scroll(x, y, delta_x, delta_y): scroll in direction
- wait(): wait for page to settle
- screenshot(): take a fresh screenshot

Guidelines:
- Take a screenshot after any action that changes page state
- If the page is loading, use wait() before continuing
- Report progress every 3-5 actions
- If the goal requires payment, personal data, or destructive action, STOP and ask

Safety:
- Never enter credentials
- Never accept unknown terms/conditions
- If uncertain, take a screenshot and describe what you see"""


def make_instructions(environment: str, width: int, height: int) -> str:
    return INSTRUCTIONS_TEMPLATE.format(environment=environment, width=width, height=height)
Note: Explicit success criteria give the model a stopping condition. Without one, it may take extra actions "to be thorough" that inflate token cost and screenshot count. "Report back when X" ends the loop cleanly.

Prevention checklist

  • Preview shape (computer_use_preview) requires display_width, display_height, and environment. GA shape (computer) has defaults.
  • Use gpt-5.4 or later — older models don't support the tool.
  • Match declared display dimensions to actual runtime viewport, or scale coordinates on execution.
  • Include an initial screenshot in the first request — model needs to see the starting state.
  • Task text should be concrete: goal, constraints, success criteria. Vague tasks produce vague actions.
  • Set truncation="auto" for long-horizon tasks so old screenshots are pruned automatically.
  • Environment choice affects model's UI assumptions — browser vs windows vs mac vs ubuntu matters.

Frequently asked questions

Do I need to run computer_use in a real browser?

Not necessarily — but you do need a screenshot source and an action executor. Headless browsers (Playwright, Puppeteer) are most common. Some teams run in Docker containers with Xvfb for full desktop simulation. What matters is that the environment you declare in the tool config matches the runtime's behavior — a headless Chrome is browser, not ubuntu.

Can I use multiple computer_use tools with different environments?

No — one computer or computer_use_preview tool per request. If you need to operate on multiple environments (e.g. a browser and a Windows app), split into separate Responses calls, one per environment.

What happens if I take a screenshot at a different resolution than declared?

Model gets confused about the coordinate space. It sees UI elements at one scale but computes clicks in the declared space. Symptoms: clicks land off-target, model expresses uncertainty about where things are, task takes many more actions than needed. Always screenshot at declared dimensions or downscale before sending.

Can computer_use interact with modals, popups, and overlays?

Yes, but modal interactions are one of the higher-failure areas — the model sometimes doesn't see a modal as blocking. Best practices: take a screenshot right after any action that might spawn a modal; include "look for modals or popups first" in your instructions; use CSS-based modal detection in your runtime to intercept and inform the model.

Does computer_use work in Batch API?

No, computer_use is inherently interactive (multi-turn with screenshots) and doesn't fit the batch model. Any batch line with a computer tool will fail validation. For non-interactive UI processing, use vision inputs with regular models and function calling.

Related errors