<!-- Markdown twin of https://pixelvault.dev/blog/compress-images-in-ci-and-pre-commit/ -->

[← Back to blog](/blog/)

By [PixelVault](/about/) November 6, 2026

# Compress images in CI and pre-commit, for developers and AI agents

Oversized screenshots creep into repos because nothing stops them. One command, run in a hook and in CI, does: no account, nothing uploaded, and safe to run again and again.

Somebody (or some coding agent) adds a screenshot to the docs, and the PNG is 4 MB. Nobody notices in review, because the diff of a binary file is just "changed". A few months later the repo is heavy, the docs page is slow, and the same images are still being downloaded by every clone.

The fix is boring and mechanical, which is exactly why it should be automated: compress images _before_ they are committed, and make CI fail when one slips through. Here is how to do both with a single command that needs no account and uploads nothing.

## The one command

The [PixelVault CLI](/docs/#cli) has an `optimize` command that runs entirely on your machine (it needs Node 20.9+):

npx pixelvault-cli optimize ./docs/images \-r \--in-place

It resizes, converts and strips metadata, and prints the saving for each file. A few properties make it safe to wire into automation:

-   **It never writes a file bigger than the original.** If an image can't be made smaller, it is left alone and reported as already optimal.
-   **It is idempotent.** Running it again over images it already compressed changes nothing, so a hook or CI check doesn't nibble at the same file every time.
-   **It refuses to lose data.** It stops before writing anything if two inputs would produce the same output file or an output would overwrite an input, and it writes through a temporary file so an interrupted run can't truncate an image.
-   **Metadata is stripped** (camera details, GPS location), after the photo's rotation is applied so it still displays the right way up. Add `--keep-metadata` if you need it.
-   **It supports JPG, PNG, WebP and AVIF.** GIF, SVG, animated images and iPhone HEIC are skipped. For HEIC, see [HEIC to URL](/tools/heic-to-url/).

Useful variations:

\# Preview the saving without touching anything npx pixelvault-cli optimize ./docs/images \-r \--dry-run \# Convert a folder to WebP, capped at 1600px wide, into a separate directory npx pixelvault-cli optimize ./shots \-r \--format webp \--max-width 1600 \--out-dir ./optimized \# Machine-readable report (always a JSON array) npx pixelvault-cli optimize ./docs/images \-r \--json

The default format is `keep`, so a PNG stays a PNG (lossless) and a JPEG stays a JPEG. Converting to WebP is usually where the biggest saving is, but it changes the file extension, so update any references to the old name.

## Compress staged images in a pre-commit hook

Save this as `.git/hooks/pre-commit` (or call it from your hook manager) and make it executable. It compresses only the images you are about to commit, then re-stages them:

.git/hooks/pre-commit

#!/bin/sh \# Compress staged images before they reach the repo. staged=$(git diff \--cached \--name-only \--diff-filter\=AM | grep \-Ei '\\.(png|jpe?g|webp|avif)$') \[ \-z "$staged" \] && exit 0 printf '%s\\n' "$staged" | tr '\\n' '\\0' | xargs \-0 npx \--yes pixelvault-cli optimize \--in-place >&2 printf '%s\\n' "$staged" | tr '\\n' '\\0' | xargs \-0 git add

`--in-place` overwrites the file with its optimized version, which is what you want for a file that is about to be committed anyway. Do not use it on files that are not under version control: there is no undo.

## Fail the build when an image is not optimized

Hooks can be skipped, so add a check in CI too. Because `optimize` is idempotent, a repo whose images are already compressed produces no changes, and anything it does change is exactly the set of images someone forgot:

.github/workflows/images.yml

name: Check images on: pull\_request jobs: optimize: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - name: Compress images run: npx --yes pixelvault-cli optimize ./docs ./public -r --in-place - name: Fail if any image was not optimized run: | git diff \--exit-code \--stat || { echo "Some images were not optimized. Run: npx pixelvault-cli optimize <dir> -r --in-place" exit 1 }

Adjust the directories to wherever your images live: the command stops with an error if a path doesn't exist. If you would rather have CI fix the images than fail, commit the result back to the branch instead of running `git diff --exit-code`.

## Make your AI coding agent do it

Coding agents are prolific screenshot-takers, so they are the most likely source of oversized images. Two ways to nudge them:

-   Add one line to your `CLAUDE.md` or `AGENTS.md`: _"Before committing images, run `npx pixelvault-cli optimize <files> --in-place`."_
-   Install the PixelVault skill (`claude plugin add pixelvault-dev/skill`), which includes `/pixelvault-optimize`. It tells the agent to preview with `--dry-run` first, prefer a separate output unless the files are committed, and report the saving.

## Prefer not to install anything?

For a handful of images there is a [free in-browser image compressor](/tools/image-compressor/). Drop your files in, pick a format, quality and maximum width, and download the results. Nothing is uploaded unless you click "Get a link".

## When you also want a hosted URL

If a compressed image needs a public URL, for example for a README, an email or a docs page that should not carry the binary at all, add `--host` to upload the optimized result and print its CDN URL:

npx pixelvault-cli optimize hero.png \--host

That step needs a PixelVault account and API key (see the [quickstart](/docs/#quickstart)). Compressing locally never does.

Want to try it on one image first? [Open the free image compressor](/tools/image-compressor/), or run `npx pixelvault-cli optimize --help` to see every option.

## More in AI agents and automation

-   [Find a screenshot by the text inside it](/blog/find-screenshots-by-their-text/) Add one flag to an upload and PixelVault writes alt text, a caption and the OCR text for the image, then lets you search your images by their words. Real output and queries.
-   [How to host an image with your coding agent](/blog/host-images-with-your-coding-agent/) You're mid-session in Claude Code or Codex and an image needs to become a URL. Here's the exact prompt to hand the agent, why the raw API call keeps tripping agents up, and the open-source skill that turns hosting into one command.
-   [Images into remote & cloud coding agents](/blog/images-into-cloud-coding-agents/) Your agent runs in the cloud or over SSH and can't take a pasted screenshot. SSH clipboard bridges like cc-clip solve half of it — here's an honest comparison, and why a hosted URL works everywhere a tunnel doesn't.

[All posts in AI agents and automation →](/blog/topics/agents/)
