Codex vs Claude

Codex Generates Raster Assets: The Repository Problem That Follows

David Guzenburg/ / 11 min read

The capability is real. The consequence is a binary file in your repository with no source, no diff and no way to make it again.

toolinggenerated codeprovenancebuild systems

A generated PNG is a source file with no source

Codex can produce raster images. Not by describing one for you to make elsewhere — it calls an image model, gets back pixels, and writes a .png into a directory. For placeholder art, icon roughs and banner comps, that closes a loop that used to involve a browser tab and a download folder.

The interesting consequence is not the picture. It is that a binary file has entered your repository with no upstream artefact, no diff, and no way to produce it again. Every other file an agent writes has a source you can read and a change you can review. This one has neither, and your repository is not set up for that.

What the tool does and where the file lands

Two paths reach the same model. Codex exposes a built-in image_gen tool that needs no API key of its own, and a $imagegen skill that wraps it with a documented workflow; the skill can also fall back to a script driving the API directly when you want control over size, quality and count. As of April 2026 the model behind it is gpt-image-2.

The part that surprises people is the destination. Generated images are written to $CODEX_HOME/generated_images/ — in practice ~/.codex/generated_images/ — not into your working tree. The skill then instructs the agent to move project-bound assets into the workspace. That move is an instruction, not a guarantee, which means the first thing to check after a generation turn is whether the file you think you have is actually in the repository or sitting in a cache directory outside it.

#!/usr/bin/env bash
# Anything newer than the last commit that is still in the cache directory
# was generated but never moved into the project.
set -euo pipefail

CACHE="${CODEX_HOME:-$HOME/.codex}/generated_images"
[ -d "$CACHE" ] || exit 0

since=$(git log -1 --format=%cI)
found=$(find "$CACHE" -type f -newermt "$since" -print)

if [ -n "$found" ]; then
  echo "generated but not committed to the workspace:" >&2
  echo "$found" >&2
  exit 1
fi

You cannot review what you cannot diff

Reviewing an agent's work means reading a change. A modified function shows up as a handful of lines with an argument you can have about them. A generated PNG shows up as Binary files differ, which is the review equivalent of a shrug.

This is the same problem as any other unreviewable artefact in a repository, and the same fix applies: make the thing that produced it reviewable instead. For code, that is the diff. For a generated image, the only reviewable object is the prompt.

Binary assets defeat the usual review gate

If your pull-request checks rely on a human reading a diff, a commit that adds twelve PNGs passes review by default. Nobody opened them. Treat any change touching binary assets as needing an explicit visual check, and say so in the template rather than hoping.

Write the prompt down beside the asset

The cheapest useful discipline is a sidecar file. When the agent generates assets/icons/cart.png, it also writes assets/icons/cart.png.json containing the prompt, the model, the date and the size. That file diffs. It is greppable. When someone asks in November why the cart icon looks different from the rest of the set, the answer is in the tree rather than in a terminal scrollback nobody kept.

{
  "prompt": "flat line icon of a shopping cart, 2px stroke, square corners,
              monochrome, no background, centred in a 24x24 frame",
  "model": "gpt-image-2",
  "generated": "2026-07-03",
  "size": "1024x1024",
  "postprocess": ["chroma-key transparency", "resize 24x24", "oxipng -o4"],
  "status": "placeholder"
}

Put the sidecar requirement in your repository's agent instructions, next to the other conventions the agent is expected to follow. It is the kind of rule an agent obeys reliably because it is mechanical, and the kind a human skips because it feels like paperwork.

Regeneration is not reproduction

Here is where the instinct to treat generated images as build output goes wrong. A build output is something you can delete and recreate. Run the same prompt through the same model twice and you get two different images. Similar, plausibly interchangeable, not identical. So the asset in your repository is not a cache of a derivable thing; it is the thing.

Which means: commit the file, do not gitignore it, and do not put image generation in your build. A CI job that regenerates icons on every run produces a site whose visual identity drifts weekly, and a diff nobody can account for. This is a narrower version of the argument in build system integration for agents — the build should be the deterministic part, and anything that is not deterministic belongs upstream of it, in the repository.

The cost line nobody budgets for

Image turns are not priced like text turns. Reported figures put generation at roughly three to five times the usage-limit consumption of an equivalent text-only turn, with per-image API pricing spanning about two orders of magnitude depending on resolution and quality. Rate limits apply separately, and new accounts start far below the standard ceiling.

The practical effect is that "generate the whole icon set" is a qualitatively different request from "rename these variables", and it will be the turn that ends your session. Generate in small batches, keep what works, and do not loop an agent on visual refinement without a stopping condition.

Transparency is a workaround, not a feature

gpt-image-2 does not produce transparent backgrounds natively. The documented approach is a two-step process using a chroma key: generate against a flat colour, then knock that colour out. It works well enough for prototyping and badly enough that you will see fringing on any asset placed over a background it was not keyed for.

Where the fringe shows up

Chroma-keyed edges look clean against the background you tested and grubby against a dark theme. If your product has a dark mode, check every keyed asset in it before deciding the pipeline works.

For anything with a hard edge and a flat fill — which is most UI iconography — a vector drawn in code sidesteps the problem entirely, and that trade-off is the subject of the companion piece on drawing in SVG.

Where generated raster assets actually belong

They are good at the thing they are good at: getting a screen to look finished before anyone has decided what it should look like. A dashboard mock with real-looking empty states reads as a design; the same screen with grey boxes reads as an argument about grey boxes. That is genuine value during build-out.

They are poor as final assets, for reasons that have nothing to do with quality. You cannot restyle them when the brand changes. You cannot ask them to match a stroke weight. You have a licensing question you probably have not asked. And they carry no source, so the next person to touch them regenerates rather than edits, and the set drifts.

Mark them as placeholders in the sidecar, and gate on that marker in CI, so the decision to ship one is made deliberately rather than by forgetting.

Binaries in git, and the moment that stops being fine

Git stores every version of every file forever. For text that is inexpensive, because a diff is small. For a 900KB PNG regenerated eleven times during a design conversation, you have permanently added ten megabytes to every clone, including the ten versions nobody wants.

A repository with a handful of generated assets does not care. A repository where an agent has been iterating on imagery for three months does, and by then the history is written. The usual answer is Git LFS, which stores pointers in the tree and objects on a server, and which introduces its own friction: contributors need the extension installed, some CI images do not have it, and a shallow clone without LFS gives you pointer files where you expected pictures.

My preference is to avoid the question by keeping the count low. Generate into a scratch directory, pick one, commit that one. The discipline that keeps generated assets reviewable — a sidecar, a marker, an explicit decision — also keeps the count low, because you cannot casually commit twelve variations of a thing that requires a manifest entry each.

Check the size before the commit, not after

A pre-commit hook that rejects any added binary over a threshold — say 250KB — unless it carries a sidecar is a two-line script and catches the entire class. Raw model output tends to arrive at 1024×1024 whether or not you needed it at 24 pixels.

Reviewing a commit that adds images

The diff is useless, so the review has to be a checklist. Six questions, none of which require opening a design tool:

Is every added binary accompanied by a sidecar naming the model, prompt and date? Is each marked as placeholder or final, deliberately? Is the resolution proportionate to the use — a 24-pixel icon should not be a megabyte? Has the asset been through the optimiser? Does anything under production source reference a placeholder path? And has one human actually looked at the images, in both themes?

That last one is the only step that costs attention, and it is the one that gets skipped, which is why the other five are worth automating. This is the same argument as reviewing agent-authored code: reserve human attention for the judgement that only a human can make, and mechanise everything around it so the judgement actually gets made.

Retrofitting provenance onto assets already in main

Most teams arrive at this article after the fact, with a directory of images of uncertain origin. You cannot recover a prompt that was never recorded, and pretending otherwise produces a manifest full of confident fiction.

What you can do is establish the boundary. Write a manifest that marks every existing asset as "provenance": "unknown, pre-policy" with the date the policy started, and require full entries for everything added after it. That is honest, it is greppable, and it converts an unbounded problem into a finite list someone can work through when the brand refresh forces the question anyway.

Then decide, once, what happens to the unknown pile. Usually the answer is that internal-facing assets stay and anything customer-facing gets regenerated or replaced deliberately before the next release that touches it.

The prompt is the asset, so version it like one

Once you are writing prompts into sidecars, a second problem appears: the prompts drift. The cart icon was generated in July with a description of your style; the bell icon in September with a slightly different one, because whoever ran it wrote the prompt from memory. The two icons do not match, and the sidecars record faithfully that they were asked to be different things.

Treat the shared part of the prompt as a file. One string, checked in, that every generation turn prefixes, describing the house style once: stroke weight, corner treatment, palette, framing, background. The per-asset prompt then describes only the subject, which is the only thing that should vary.

House style for all generated iconography. Prefix every icon prompt
with this block verbatim; do not paraphrase it.

Flat two-dimensional line art. Uniform 2px stroke at 24px nominal size,
no fills, no gradients, no shadows. Square line caps, 2px corner radius.
Single subject centred in a square frame with 2px optical padding.
Pure white background, no scene, no perspective, no text.

The instruction not to paraphrase matters more than it looks. An agent asked to include a style description will helpfully improve it, and a helpfully improved style description is a different style. Pin the string, and when the style genuinely changes, change the file in a commit that also regenerates the set.

This is the same discipline as any other shared constant, and it fails the same way when it is prose living in someone's head. The general case — what belongs in a repository's instruction files, and why duplicating it makes agents worse rather than better — is the argument in README duplication degrades agents. A style prompt is that argument applied to pixels.

What I actually do

Generated assets are allowed, in one directory, with a sidecar each, marked as placeholders by default. The style prefix is a file. The pre-commit hook rejects binaries over 250KB without a sidecar. CI fails if production source references the placeholder path, and prints the outstanding count on every build. Nothing regenerates in CI, ever.

That setup took an afternoon and it has never once needed maintenance, which is the property I care about. The alternative — a policy in a wiki, and reviewers who are supposed to notice — needs maintenance constantly and works about half the time, because noticing a PNG in a forty-file diff is not a thing humans do reliably at five in the afternoon.

Takeaway

Codex generating a PNG is a real capability with a real repository problem attached: the file arrives with no diff, no source and no reproducibility. Write a sidecar with the prompt and model beside every generated asset, commit the image rather than treating it as build output, mark placeholders explicitly and gate on that marker, and budget for image turns separately — they consume usage several times faster than the text work around them.

Keep reading
Codex vs Claude

Why Claude Code Draws in SVG: Missing Tool, Not Missing Eyes

The reason an agent writes vector rather than raster is a tool list, not a rendering engine. What follows: source that diffs, deterministic rasterisation in the build, and where vector genuinely loses.

Codex vs Claude

Canvas or Static Asset: Deciding Where the Picture Gets Computed

An agent will offer a canvas script or a generated image and the choice is about cost distribution, not appearance. Opacity to accessibility and search, missing fallbacks, and the devicePixelRatio bug in every generated canvas.

Codex vs Claude

Optimising SVG: The Byte Count Reports the Saving, Not the Damage

Default optimiser settings strip viewBox, mangle referenced ids, merge animated paths and round flush edges apart. A safe checked-in configuration, and verifying by rendered pixels rather than file size.

Codex vs Claude

The Favicon Set Is Four Files: Cutting the Package an Agent Gives You

Generated favicon packages encode a decade of obsolete device workarounds. The set that is actually requested, an SVG icon that follows the colour scheme, the iOS transparency trap, and a CI check for paths that quietly return HTML.

← High-Volume Work and the “58% Cheaper” Claim  ·  Database Access: You Wanted the Schema and You Granted the Rows →

All codex vs claude articles  ·  Every article