An AI coding agent will happily refactor your stylesheet, swap a component library, or bump a dependency that ships its own CSS. What it cannot do, on its own, is look at the result.
That is the gap. The tests pass, the build is green, the diff in the terminal looks reasonable — and the pricing page is broken at 375px. Somebody has to open a browser to find out, which means the fastest part of the workflow now ends in the slowest part.
We built a Claude Code plugin to close it. The Diffy plugin gives Claude ten skills for visual regression testing, so the agent that made the change can capture the page, compare it against a baseline, and report which pages and breakpoints moved — inside the same session, before you ever alt-tab.
The plugin is open source under MIT at github.com/DiffyWebsite/diffy-skills.
Install it in two commands
Claude Code plugins are distributed through marketplaces. Add ours, then install the plugin:
/plugin marketplace add DiffyWebsite/diffy-skills
/plugin install diffy@diffy
The skills arrive namespaced — /diffy:visual-diff, /diffy:compare-environments, and so on — and Claude also invokes them on its own when your request calls for one. “Check whether my header change broke anything” is enough; you do not have to remember a command name.
Two prerequisites before the first run:
# 1. Install the diffy CLI (pick a bin directory you can write to)
wget -O /usr/local/bin/diffy https://github.com/diffywebsite/diffy-cli/releases/latest/download/diffy.phar && chmod a+x /usr/local/bin/diffy
# 2. Authenticate with your API key from https://app.diffy.website/#/keys
diffy auth:login <API_KEY>
That is the whole setup for remote capture. Local capture needs one more thing, which is the next section.
Two capture modes: local and remote
Most visual testing tools make you choose between “runs on my machine” and “runs in the cloud” once, at adoption time. The plugin ships both, and picks per request.
| Local | Remote | |
|---|---|---|
| Where screenshots are taken | Your machine | Diffy’s servers |
| What it captures | A running dev server (localhost:3000) | A deployed URL |
| Needs Docker | Yes | No |
| Typical use | Mid-edit, before you push | Prod vs staging, PR environments, CI |
| Skills | visual-diff, upload-screenshot | compare-environments, remote-screenshot |
Local mode captures inside Diffy’s published diffywebsite2/screenshot-worker container — the same Chromium, fonts, and Playwright runtime Diffy runs in production. That detail matters more than it sounds. A screenshot taken by your local Chrome on macOS does not render like a screenshot taken by a headless Linux browser in a data centre; font hinting alone will paint half the page red in a diff. Because local capture runs in the production rendering runtime, a screenshot from your laptop lines up with a Diffy baseline.
The trade is Docker. The first local capture runs a one-time provisioning step that pulls the image (about 1.1 GB), clones the worker code into ~/.cache/diffy/diffy-worker, and installs its dependencies inside the container. Later runs reuse all of it. The container reaches your dev server through host.docker.internal, and the plugin rewrites localhost and 127.0.0.1 in the URL for you.
Remote mode needs none of that — no Docker, no Node, no local browser. Diffy screenshots both sides on its own infrastructure. The requirement there is reachability: the URLs must be resolvable from Diffy’s network, either publicly or with the HTTP basic-auth credentials you pass in.
When a request is ambiguous — a bare “compare my UI” with no dev server or environment named — Claude asks which mode you want rather than guessing.
Workflow 1: catching regressions while you edit
This is the loop the plugin was built for, and it runs in two phases.
Before you touch anything, ask for a baseline. Claude captures the current state of your running app and remembers the screenshot ID in .diffy-visual/state.json:
Take a Diffy baseline of localhost:3000 before we start.
Then make the change — or have Claude make it. When you are ready, ask again:
Now compare against the baseline.
Claude re-captures, creates the diff, waits for it to finish, and reports back with a verdict like “3 of 6 page/breakpoint views changed; largest: /pricing at 375px (12.4%)” plus a shareable report link.
The part that keeps this maintainable: there is no config file. Pages, breakpoints, masks, delays, scroll behaviour, login steps, cookies, and custom JS all come from your Diffy project settings, and the worker re-bases each configured page onto your local URL. You maintain capture behaviour in one place, and it applies identically whether the screenshot is taken on your laptop or in production. When you need to change it, /diffy:update-project-settings edits the project rather than a file in your repo.
Workflow 2: comparing two deployed environments
The remote counterpart is a single request. Prod against staging:
Compare production and staging for project 12345.
Or a pull request environment against production:
Compare prod against https://pr-123.example.com.
Diffy screenshots both sides server-side and returns a diff ID. For CI, the underlying CLI accepts --commit-sha, which attaches a GitHub check to that commit — the same mechanism described in our post on running visual regression tests from a GitHub and Pantheon workflow. Results can also come back as JUnit XML, which most CI systems will render as test output without further work.
What Claude actually reports
Whichever workflow produced the diff, /diffy:get-diff-info turns it into something readable:
- Overall changed percentage —
0means nothing moved. - A per-page, per-breakpoint table, sorted by change percentage descending, so the worst regression is the first thing you read.
- A shareable report link to the visual diff, for the teammate who wants to see it rather than read about it.
- JUnit XML, when you ask for it.
Claude reads the JSON and summarizes it. You get the table, not a wall of nested objects.
The full skill list
Ten skills, composable. The two orchestrators do the whole job end to end; the rest are building blocks you can chain yourself.
| Skill | Mode | What it does |
|---|---|---|
/diffy:visual-diff | local | End-to-end before/after regression on your local UI |
/diffy:compare-environments | remote | Compare two environments server-side |
/diffy:create-project | either | Create a project from a base URL, pages, and breakpoints |
/diffy:get-project-settings | either | Show a project’s settings as export YAML |
/diffy:update-project-settings | either | Edit pages, environments, breakpoints, masks, login, schedule |
/diffy:upload-screenshot | local | Capture a running app in the worker container and upload it |
/diffy:remote-screenshot | remote | Ask Diffy to screenshot an environment on its servers |
/diffy:get-screenshot-info | either | Check whether a screenshot set finished capturing |
/diffy:compare-screenshots | either | Create a diff from two existing screenshot set IDs |
/diffy:get-diff-info | either | Summarize a diff: percentages, table, report link, JUnit |
What it will not do
Two limits worth stating plainly, because both are deliberate.
The plugin never deletes projects. Every skill that touches project settings carries an explicit instruction not to delete a Diffy project — not through the CLI, not through a direct API call, not through the web UI. If you ask, Claude refuses and points you at the dashboard. The repository backs that up for anyone hacking on the plugin itself with a permissions.deny list in .claude/settings.json that blocks delete-shaped diffy commands outright, and you can copy the same rules into your own project settings if you want a guardrail that does not depend on the model behaving. Handing an agent a destructive verb on your test history is not a feature.
It is not a local pixel diff. Diffy is a cloud service, and these skills use it — you need an account, an API key, and a project. In local mode the screenshots are captured on your machine and uploaded; the comparison itself happens on Diffy’s side, along with everything that rides on it — including the AI screenshot checks that flag cookie banners and CDN challenge pages before they turn into false positives.
One housekeeping note: the skills write small metadata files under .diffy-skills/ and .diffy-visual/. Add both to .gitignore — Claude offers to do it for you on first run.
Getting started
If you already use Diffy, the plugin is two commands away and works against your existing projects and their existing settings. If you do not, create a free account, set up a project with the pages and breakpoints you care about, and point the plugin at it.
And if you are new to the practice itself, start with what visual regression testing is and when it pays off — the plugin makes the loop faster, but the reason to run it at all does not change: false positives are the thing that kills adoption, and a well-configured project is what prevents them.
Issues and pull requests are welcome at DiffyWebsite/diffy-skills.
