Skip to main content
hooksSource-backedReview first Safety Privacy

Screenshot Visual Regression - Claude Code Hook

PostToolUse hook that catches unintended UI changes by pixel-diffing a just-saved screenshot against its baseline with odiff. When a PNG lands in a screenshots or snapshots directory, the hook finds the matching baseline, compares the two images, writes a diff image when they differ, and reports the pixel-difference summary so a visual regression is surfaced the moment the screenshot is updated. Advisory only; it never overwrites the screenshot or the baseline.

by techforgeworks·added 2026-06-04·
Claude Code
HarnessClaude Code
Trigger:PostToolUse
Review first review before installing

Open the source and read safety notes before installing.

Safety notes

  • Runs after every Write, Edit, and MultiEdit but only acts on PNG files saved under a screenshots, snapshots, or visual directory, and reads the screenshot and its baseline image from disk.
  • Install this project-local command only in project settings (`.claude/settings.json`). Do not place `$CLAUDE_PROJECT_DIR/.claude/hooks/screenshot-visual-regression.sh` in user/global settings, because that would allow each opened project to supply the executed script.
  • When a baseline exists and odiff is installed, it writes a diff image next to the screenshot (file.diff.png) describing the difference; it never deletes or overwrites the screenshot or the baseline.
  • Advisory and always exits 0 - it reports a visual difference but never blocks the write or updates the baseline on its own.
  • Fails open: with no baseline, no odiff, or no jq, it prints a short hint and does nothing else.

Privacy notes

  • Reads only local image files (the new screenshot and its baseline); it makes no network calls.
  • Writes one local diff image (file.diff.png) next to the screenshot when a difference is found; it writes no other logs.
  • File paths and odiff's pixel-difference summary are printed to local hook stderr and may reveal project structure.

Prerequisites

  • Claude Code CLI with hooks enabled.
  • bash and jq on PATH; the hook fails open and stays silent when jq is missing.
  • odiff installed (npm i -g odiff-bin) for the image comparison; without it the hook prints an install hint and exits.

Schema details

Install type
cli
Troubleshooting
No
Source repository stats
Scope
Source repo
Runtime and command metadata
Trigger
PostToolUse
Script language
bash
Script body
#!/usr/bin/env bash
set -u

# Claude Code PostToolUse hook. Pixel-diffs a just-saved screenshot against its
# baseline using odiff and reports a visual regression. Advisory only - it
# always exits 0, never overwrites the screenshot or baseline - and fails open
# when jq or odiff is unavailable.

command -v jq >/dev/null 2>&1 || exit 0

INPUT=$(cat)
FILE=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')

# Only act on PNG screenshots saved in a visual-test location.
case "$FILE" in
  *.png) : ;;
  *) exit 0 ;;
esac
case "$FILE" in
  *screenshots/*|*snapshots/*|*__snapshots__/*|*__screenshots__/*|*/visual/*) : ;;
  *) exit 0 ;;
esac
[ -f "$FILE" ] || exit 0

dir=$(dirname "$FILE")
base=$(basename "$FILE")

baseline=""
for c in "$dir/baseline/$base" "$dir/baselines/$base" "$dir/__baselines__/$base" "${FILE%.png}.baseline.png"; do
  [ -f "$c" ] && { baseline="$c"; break; }
done

if [ -z "$baseline" ]; then
  echo "Visual regression: no baseline found for $FILE - if this is the intended reference, save it as the baseline." >&2
  exit 0
fi

if ! command -v odiff >/dev/null 2>&1; then
  echo "Visual regression: install odiff (npm i -g odiff-bin) to diff $base against its baseline." >&2
  exit 0
fi

diff_out="${FILE%.png}.diff.png"
summary=$(odiff "$baseline" "$FILE" "$diff_out" 2>&1)
if [ "$?" -ne 0 ]; then
  echo "Visual regression: $base differs from its baseline." >&2
  printf '%s\n' "$summary" | while IFS= read -r line; do
    [ -n "$line" ] && echo "   $line" >&2
  done
  echo "   Review $diff_out; update the baseline only if the change is intended." >&2
fi

exit 0
Full copyable content
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/screenshot-visual-regression.sh"
          }
        ]
      }
    ]
  }
}

About this resource

Features

  • Catches unintended UI changes by pixel-diffing a just-saved screenshot against its baseline with odiff, a fast image-comparison tool.
  • Triggers only on PNG screenshots saved under a screenshots, snapshots, __snapshots__, or visual directory.
  • Writes a diff image next to the screenshot and reports odiff's pixel-difference summary when the images differ.
  • Advisory only — it always exits 0, never blocks the write, and never updates the baseline for you.
  • Fails open: with no matching baseline, no odiff, or no jq, it prints a short hint and stops.

How it works

On PostToolUse, the hook checks whether the saved file is a screenshot PNG in a visual-test location. It looks for a baseline at baseline/<name>, baselines/<name>, __baselines__/<name>, or <name>.baseline.png, then runs odiff <baseline> <new> <diff>. odiff exits non-zero when the images differ; the hook then reports the difference and points at the generated diff image so the change can be reviewed and the baseline updated only if intended.

Use cases

  • Surface a layout or styling regression the moment an updated screenshot is written during a UI change.
  • Add a fast local visual check ahead of a full visual-regression suite in CI.
  • Keep agent-driven UI edits honest by flagging pixel changes against an approved baseline.

Installation

  1. Create the hooks directory: mkdir -p .claude/hooks
  2. Create the hook file: touch .claude/hooks/screenshot-visual-regression.sh
  3. Paste the script body into that file and make it executable: chmod +x .claude/hooks/screenshot-visual-regression.sh
  4. Install odiff: npm i -g odiff-bin
  5. Add the configuration below to the project settings file, .claude/settings.json. Do not add this project-local $CLAUDE_PROJECT_DIR/.claude/hooks/... command to user/global settings (~/.claude/settings.json); global hooks should only point to trusted user-owned scripts outside the project.

Requirements

  • Claude Code CLI with hooks enabled
  • bash and jq
  • odiff (npm i -g odiff-bin)
  • A baseline image stored alongside each screenshot (in baseline/, baselines/, __baselines__/, or as name.baseline.png)

Hook configuration

This configuration is for project settings (.claude/settings.json) only. For user/global settings, point to a trusted script in a user-owned location such as ~/.claude/hooks/ instead of using $CLAUDE_PROJECT_DIR.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/screenshot-visual-regression.sh"
          }
        ]
      }
    ]
  }
}

Limitations

  • Compares against a baseline by filename convention; projects that store baselines elsewhere need the baseline lookup paths adjusted.
  • Operates on PNG screenshots; other image formats need odiff's format support and an added path pattern.

Source and references

#visual-regression#screenshots#testing#ui#odiff

Source citations

Signals

Loading live community signals…

More like this, weekly

A short, calm digest of reviewed Claude resources. Unsubscribe any time.