ov-build/skills/merge/SKILL.md
Post-build layer optimization via merging consecutive small layers. MUST be invoked before any work involving: ov image merge command, image layer reduction, merge configuration, or post-build optimization.
npx skillsauth add overthinkos/overthink-plugins mergeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Invoked as ov image merge [<image>]. See /ov-build:image for the family overview.
Reduces image layer count by merging consecutive small layers. Uses go-containerregistry to load the image, groups consecutive layers below a size threshold, deduplicates filesystem entries (last writer wins), and reconstructs the image. Idempotent -- safe to run multiple times.
| Action | Command | Description |
|--------|---------|-------------|
| Merge single image | ov image merge <image> | Merge layers in specified image |
| Dry run | ov image merge <image> --dry-run | Show what would be merged without changing anything |
| Custom threshold | ov image merge <image> --max-mb N | Set max layer size for merge candidates (default: 128 MB) |
| Merge all auto | ov image merge --all | Merge all images that have merge.auto: true |
# Merge consecutive small layers in an image
ov image merge sway-browser-vnc
# Preview without changing
ov image merge sway-browser-vnc --dry-run
# Only merge layers smaller than 64 MB
ov image merge sway-browser-vnc --max-mb 64
# Merge all images that opt in via image.yml
ov image merge --all
images:
sway-browser-vnc:
merge:
auto: true # Include in `ov image merge --all`
max_mb: 128 # Size threshold (default: 128)
go-containerregistrymax_mbov image buildOCI/Docker images use special "whiteout" files to represent file deletions across layers. When merging layers, these must be handled correctly to prevent EEXIST errors during overlay unpack.
Three cases:
Regular whiteout — A file .wh.<name> in layer N indicates that <name> was deleted. If an earlier layer contains the original file, the merge suppresses the original (keeps the whiteout marker so the deletion is preserved in the merged output).
Opaque whiteout — A file .wh..wh..opq in directory D means "the entire directory was replaced." All entries under D from earlier layers are suppressed. Only entries from the layer containing the opaque marker (and later layers) survive.
Reintroduction supersedes whiteout — If a file is deleted (whiteout in layer M) then re-created (same path in layer N, where N > M), the whiteout is suppressed and the re-introduced file is kept. This prevents the merged layer from containing both a file and its own whiteout, which would cause overlay unpack failures.
Why this matters: Without whiteout suppression, merged layers could contain contradictory entries (a file and its .wh.* marker coexisting), causing EEXIST errors when the container runtime unpacks the layer onto an overlay filesystem.
podman load: file exists on multi-stage RPM imagesIn some images (observed empirically against immich:2026.128.x), the post-build merge step succeeds at the Go-level (every merge group emits a valid tarball that passes internal consistency checks) but podman load rejects the final docker-archive with:
unpacking failed (error: exit status 1; output: file exists)
ov: error: post-build merge optimization failed (image is functional but unmerged): podman load: exit status 125
Diagnostic: set OV_MERGE_KEEP_TMP=1 and re-run `ov image merge <name>` to capture the failing /tmp/ov-merge-*.tar.
This is a known limitation against multi-stage RPM-installed images; the build itself succeeded and the image at this tag is correct.
Investigation (May 2026) ruled out every canonical mergeLayers bug class:
Linkname targets present in their own layer)The trigger appears to be a podman-side overlay-unpack quirk under specific layer-content patterns — multi-stage RPM-installed images that touch /usr/lib/sysimage/rpm/* in 6+ source layers consistently reproduce. Possibly related to the known podman-5.7.x blob-reuse race (storage_dest.go:TryReusingBlobWithOptions) documented in /ov-build:build, but unconfirmed against 5.8.x.
The failure is non-fatal. mergeAfterBuild (ov/build.go:178-186) treats merge failure as a non-fatal warning. The build itself returns 0, the image is tagged at its CalVer, every individual layer digest is valid, and ov start runs the unmerged image with no functional difference — only the layer count is higher than ideal (~39 layers instead of the ~12 a successful merge would produce).
OV_MERGE_KEEP_TMP=1When merge fails and you want to capture the failing tarball for inspection or forensic analysis, set OV_MERGE_KEEP_TMP=1:
OV_MERGE_KEEP_TMP=1 ov image merge <name>
On failure the tarball is left at /tmp/ov-merge-<random>.tar (path printed to stderr) instead of being cleaned up. The tar is a docker-archive — extract manifest.json to see the layer chain, then tar -xzf <hash>.tar.gz on individual layers to inspect their contents.
For forensic analysis of layer contents:
# List paths within a single layer
zcat <hash>.tar.gz | tar -tvf -
# Find paths that appear in multiple layers (the cross-layer overlap pattern)
for f in *.tar.gz; do
zcat "$f" | tar -tf - | sed "s|^|$f\t|"
done | awk -F'\t' '{cnt[$2]++} END {for (p in cnt) if (cnt[p]>1) print cnt[p], p}' | sort -rn | head
Source: ov/merge.go:saveImageToDaemon (the keep-on-fail logic; loaded flag gates the cleanup defer).
ov image merge resolves image.yml via os.Getwd(). Override with -C <dir> / --dir <dir> / OV_PROJECT_DIR=<dir>. See /ov-build:image "Project directory resolution".
ov image family siblings/ov-build:image -- Family overview + image.yml composition reference/ov-build:build -- Building images (merge runs inline after each build level)/ov-build:generate -- Containerfile generation/ov-build:inspect -- Inspect merged images/ov-build:list -- Enumerate images before merging/ov-build:new -- Scaffold new layers/ov-build:pull -- Pull prebuilt images into local storage/ov-build:validate -- Validate before merging/ov-build:layer -- Layer authoring (layer size affects merge behavior)development
Claude Code multi-agent support in Overthink — sub-agents, dynamic workflows, and agent teams, and how each drives the existing `ov eval` disposable beds to test and verify. MUST be invoked before authoring or invoking an ov sub-agent / dynamic workflow / agent team, wiring agent-lifecycle hooks, or asking "which primitive should drive the R10 beds?".
tools
Mounts a virtiofs share tagged `workspace` at /workspace inside a VM guest via a systemd .mount unit. Use when a kind:vm entity shares a host directory into the guest and you need it auto-mounted (and re-mounted at every boot).
development
MUST be invoked before any work involving: the `kind: android` schema kind, a `target: android` deploy, the `apk:` layer package format (installing Android apps declaratively), AndroidDeployTarget, an in-pod emulator OR a remote/physical adb-endpoint device, or nested `pod → android` deployment. The first-class Android device + app surface that sits above `ov eval adb`/`appium`.
tools
Use when committing, branching, pushing, merging, tagging, creating PRs, or approving/merging PRs with gh — the feat/-branch, R10-gated, never-force-push landing workflow across the main repo + the plugins submodule + image/<distro> submodules. Covers sync-to-upstream, branch/worktree pruning, the fork+PR path for contributors without write access, and cross-repo @github landing order.