Claude Code Screenshots: Stop Wasting Tokens on Pixels

Claude Code screenshot workflows burn tokens uploading raw pixels that the model then has to guess at—"is this a button or a div?"—while you type paragraphs explaining which element you meant. The real problem: screenshots are dumb images; Claude Code needs the named UI element (role, label, parent chain, frame) to edit the right component, and pixels don't carry that data. Most developers waste time re-describing what's already on screen because the screenshot doesn't tell the agent what it's looking at.
We're conditioned to think screenshots are helpful context. They're not. They're expensive, ambiguous pixel dumps that force the agent to infer structure from visual cues—and when it guesses wrong, you waste another round trip clarifying "no, the other button."
Why do Claude Code screenshots fail to identify UI elements?
Claude Code's screenshot skill (and similar tools in Cursor, Zed, Codex) capture a bitmap and upload it to the model. The model analyzes pixel patterns—color, shape, text rendered as glyphs—and guesses what each region represents. No DOM. No accessibility tree. No element identity.
For web UIs, the agent might infer "that looks like a button" from rounded corners and shadow, but it can't distinguish <button id="submit"> from <div class="btn"> from <a> styled as a button. For native macOS apps (Xcode, Finder, Mail, Slack's Electron shell), there's zero markup to infer from. The model sees pixels, not NSButton with a label and action selector.
When you say "fix the submit button," Claude Code parses your codebase for strings like "submit" and makes a best guess. If your repo has three buttons with "submit" in the label, it picks one—often the wrong one—because the screenshot gave it no tie-breaker.
Screenshots uploaded to Claude Code count against your token limit (vision tokens are ~765 tokens per image at high detail in GPT-4o; Claude 3.5 Sonnet charges similar rates). A single 1920×1080 screenshot costs ~1,200 tokens. Five screenshots in a thread = 6,000 tokens before you've written a word.
The macOS Accessibility API—the same framework VoiceOver uses—exposes every on-screen UI element as an AXUIElement with a role (button, text field, menu item, web area), label (the visible or semantic title), value (current text/state), and frame (exact pixel bounds). AXUIElementCopyElementAtPosition(x, y) returns the element under any pointer coordinate, not a pixel guess.
When you point at a button and the AX tree says AXButton, title "Submit", parent AXGroup "Form Actions", frame {520, 680, 120, 44}, that's executable context. Claude Code can search the codebase for "Submit" scoped to form-action UI, find the handler, and edit the right callback. Pixels can't give you that path.
How does point-and-speak resolve named elements without screenshots?
Hold ⌥⌘A, circle or point at any UI element, and speak your instruction. PinVari captures three simultaneous streams:
- On-device voice transcription (Apple Speech framework) with word-level timestamps.
- A pointer trail (x, y, timestamp) recorded every 16ms while you mark.
- An AX element resolution the instant you release the hotkey.
When you say "change this button to green," the word "this" has a timestamp. PinVari walks the pointer trail, finds where the pointer was at that exact moment, calls AXUIElementCopyElementAtPosition, and retrieves the AXButton with role/label/value/frame. That element gets bound to the deictic word—"this" now means AXButton "Submit" at {520, 680, 120, 44}.
The capture then reads the window's full accessibility text (the content VoiceOver would read aloud), extracts the browser's real URL from the AXWebArea if it's a web view, and falls back to on-device Vision OCR on surfaces the AX tree doesn't cover (canvas elements, some Electron internals, games).
PinVari doesn't upload pixels to an external API. Transcription and OCR are on-device (Apple frameworks). The screenshot is cropped to the circled region and handed to your local agent over MCP—no cloud hop, no API keys required.
The output: a resolved, named, executable instruction with the element path, your spoken sentence, the region you marked, and a cropped screenshot showing exactly what you circled. That's what lands in Claude Code's MCP tool pinvari_next_instruction.
What is the Claude Code screenshot skill actually doing?
When you type screenshot in the Claude Code CLI or click the camera icon in the GUI, it calls the underlying OS screenshot API (screencapture on macOS, similar on Windows), saves a temp PNG, encodes it to base64, and uploads it to the model endpoint as a vision message. The model receives pixels, no metadata beyond dimensions.
Claude Code doesn't parse the AX tree or DOM before uploading. It doesn't extract window titles, element labels, or text content. It ships the bitmap and hopes the model can OCR text and infer UI structure from visual patterns.
For text-heavy UIs (code editors, terminal output, browser DevTools), the model does okay—it can read rendered glyphs via OCR. For iconography, custom controls, or ambiguous regions (is that a disabled button or a label?), it guesses. When you circle a region in a screenshot and say "fix this," you're asking the model to infer "this" from pixel proximity—not a guaranteed win.
The screenshot skill is useful for showing what broke (a layout bug, a color mismatch), but it doesn't name what broke. You still have to type "the submit button in the form footer" to disambiguate.
How do you give Claude Code the named element instead of pixels?
Install PinVari, add the MCP server to Claude Code (claude mcp add pinvari), and point-and-speak at the UI. The MCP tool pinvari_next_instruction returns:
{
"instruction": "change this button to green",
"element": {
"role": "AXButton",
"label": "Submit",
"value": null,
"frame": {"x": 520, "y": 680, "width": 120, "height": 44},
"parent_chain": ["AXWindow", "AXGroup", "AXGroup"],
"confidence": 0.92,
"provenance": "circled"
},
"screenshot_crop": "<base64 PNG cropped to circled region>",
"window_text": "Full form text content...",
"url": "https://app.example.com/settings"
}
Claude Code sees the named element with role and label, the instruction bound to it, the visual crop for confirmation, and the window's full text (not OCR'd—read directly from the AX tree). It can now search the codebase for "Submit" scoped to AXButton in a settings form, find the click handler, and edit the right function.
The screenshot is there for visual confirmation, not as the primary signal. If you captured a native macOS app (Xcode, Finder, Mail), the AX tree gives Claude Code the element identity that pixels can't. If you captured a web app, the AXDOMIdentifier and AXDOMClassList attributes (Chromium-specific AX extensions) map the AX element back to the DOM node—<button id="submit-btn" class="primary">.
For browser-based UIs, PinVari reads the real URL from the AXWebArea element—not inferred from window title or OCR'd from the address bar. Claude Code gets https://app.example.com/settings, not a guess.
When Claude Code finishes the edit, it calls pinvari_mark_done to close the capture. The next ⌥⌘A starts fresh. No token pile-up from screenshot history.
Can you use PinVari with other AI coding agents?
Yes. The MCP server ships in the app bundle and runs locally on 127.0.0.1. Any agent that speaks MCP can call pinvari_next_instruction and pinvari_mark_done:
- Claude Code (Desktop or CLI):
claude mcp add pinvari, then the tools appear in the agent's skill list. - Cursor: Add the MCP server to
~/.cursor/mcp_settings.json. - Zed: Install the MCP extension and point it to the PinVari server.
- Codex (or any MCP-compatible agent): Same pattern—configure the server URL, call the tools.
The MCP protocol is open. PinVari doesn't care which agent consumes the instruction; it just hands over the resolved element and waits for mark_done.
If you're not using an agent, PinVari also routes captures to Linear (creates an issue with the screenshot, instruction, and element metadata attached) or GitHub (opens an issue draft), or exports a shareable Markdown page. Point-and-speak to file bugs without typing descriptions.
What about Claude Code screenshots on Windows?
PinVari is macOS-only (Sonoma 14+ on Apple Silicon and Intel). Windows doesn't expose a universal accessibility API equivalent to macOS AX—UI Automation exists but adoption is inconsistent, and querying elements under a pointer requires COM interfaces that third-party apps (Electron, Qt, custom toolkits) often don't implement.
It doesn't resolve named AX elements—it sends the screenshot, window text (via platform-specific scraping), and pointer position.
For Windows users in Claude Code, the default screenshot skill is still your best option, with the caveats above: you'll burn tokens and need to type element descriptions to disambiguate. Some teams script AutoHotkey + UI Automation queries to extract element metadata and paste it into Claude Code, but that's brittle and app-specific.
Comparison: screenshot workflows for Claude Code
| Method | Element Identity | Token Cost | Platforms | Requires Typing |
|---|---|---|---|---|
screenshot skill (native) | None (pixel OCR) | ~1,200 per image | macOS, Windows, Linux | Yes (to name element) |
| PinVari + MCP | Named AXUIElement, role/label/frame | 0 (on-device) | macOS 14+ | No (point-and-speak) |
| Manual screenshot + paste | None | ~1,200 per image | Any | Yes (full descriptions) |
| Jam.dev (browser only) | DOM element (browser extension) | 0 (Jam hosts) | Browser only | Partial (click-to-mark) |
PinVari is the only tool that resolves the named macOS AX element on-device and hands it to Claude Code without uploading pixels to an external API.
How much do Claude Code screenshots actually cost in tokens?
Claude Code uses your Claude API key (or Anthropic's hosted Claude in the Desktop app). Vision messages cost per image based on resolution and detail level. At high detail (the default for screenshots):
- 1920×1080 screenshot ≈ 1,200 tokens (Claude 3.5 Sonnet, similar to GPT-4o vision pricing).
- 2560×1440 (Retina display) ≈ 2,100 tokens.
- 3840×2160 (4K) ≈ 4,500 tokens.
If you're debugging a UI bug and paste five screenshots in a thread to show the problem evolving, that's 6,000–22,500 tokens before the agent writes a single line of code. Claude Code's context window (200k tokens for Sonnet 3.5) absorbs it, but you pay per token. Screenshots waste Claude Code tokens breaks down the math.
PinVari crops the screenshot to the region you circled (often 200×200 to 600×600 pixels) and includes it for visual confirmation, but the primary signal—the named element—costs zero tokens because it's structured JSON, not a bitmap. A typical pinvari_next_instruction payload is 800–1,500 tokens total (instruction + element + cropped image), vs. 1,200–4,500 for a full-screen screenshot that says less.
Do you need to install anything for PinVari to work with Claude Code?
Yes, two steps:
- Download PinVari (notarized DMG, one-time $39 launch price for the first 500 licenses, then $59) from pinvari.com/#pricing. Drag to
/Applications, launch, grant Accessibility and Screen Recording permissions in System Settings > Privacy. - Add the MCP server to Claude Code:
claude mcp add pinvariin the CLI, or configure it manually in the Desktop app's MCP settings.
PinVari runs a local MCP server on 127.0.0.1:8765 (configurable). The pinvari_next_instruction and pinvari_mark_done tools appear in Claude Code's skill list. No API keys to configure—PinVari doesn't call external services.
The app bundle includes the on-device transcription model (Apple Speech framework) and Vision OCR. First launch downloads the speech model for your system language (~50MB). After that, everything runs offline.
PinVari does not require a Claude API key. You bring your own agent (Claude Code, Cursor, Zed, Codex). The MCP server is a local pipe; it doesn't authenticate or meter usage.
If you're not using Claude Code, you can still point-and-speak and file the capture to Linear, GitHub, or a shareable page—no agent required.
FAQ
Can Claude Code screenshot native macOS apps like Xcode or Finder?
Yes, the screenshot skill captures any on-screen window, including native macOS apps. But it uploads pixels with no element metadata. Claude Code sees a bitmap of Xcode's UI and has to infer "that's a sidebar disclosure triangle" from pixel patterns. PinVari reads the AX tree and tells Claude Code "that's an AXDisclosureTriangle, labeled 'Targets', currently expanded"—zero ambiguity.
Does the screenshot in Claude Code CLI work the same as the Desktop app?
Yes, both call the same underlying screenshot API and upload the image to the model endpoint. The CLI types screenshot as a command; the Desktop app has a camera icon. Token cost and pixel-guessing limitations are identical. Neither parses the AX tree before uploading.
Can you use PinVari without Claude Code?
Yes. PinVari routes captures to Linear (creates an issue), GitHub (opens an issue draft), or exports a shareable Markdown page with the screenshot, instruction, and element metadata. You don't need an agent to use the point-and-speak capture. The MCP server is optional—install it only if you're routing to Claude Code, Cursor, or another agent.
How does PinVari handle Electron apps where Accessibility is empty?
Chromium (and thus Electron) builds its AX tree lazily—only when VoiceOver or an assistive app queries it. PinVari sets AXManualAccessibility on the target window and retries AXUIElementCopyElementAtPosition with a ~150ms backoff until a labeled element appears. For Electron apps that never expose AX (some custom canvas UIs), PinVari falls back to on-device Vision OCR and returns the OCR'd text with a lower confidence score. How AI agents know which UI element explains the fallback chain.
Does PinVari upload screenshots to the cloud?
No. Transcription and OCR are on-device (Apple Speech and Vision frameworks). The screenshot stays local unless you explicitly route the capture to Linear, GitHub, or a shareable page—and even then, you trigger the upload by clicking "File to Linear" or "Share". The MCP server hands the screenshot to your local agent over 127.0.0.1; it never leaves your machine unless your agent uploads it (which Claude Code does if you paste it into a message, but PinVari isn't the one uploading).
Can you point-and-speak at multiple UI elements in one capture?
Yes. Hold ⌥⌘A, circle the first element, keep the hotkey held, circle a second element, then release (or press ⏎ to finish). Each circled region resolves to its own named AXUIElement. The capture includes all marked elements, the full instruction, and a screenshot showing all circles. Claude Code gets a list of elements in the pinvari_next_instruction payload—useful for "change these three buttons to match the design" instructions.
Hand your agent the exact element
PinVari resolves what you point at into a named, executable instruction — on-device, no keys, your own agent. If you run Claude Code, it is one command.
claude mcp add pinvariGet PinVari — $39 →


