Skip to content

feat(elixir): add run-prod-compile to catch production-build breakage at PR time - #69

Merged
nkg merged 1 commit into
mainfrom
feat/prod-compile-check
Aug 25, 2026
Merged

feat(elixir): add run-prod-compile to catch production-build breakage at PR time#69
nkg merged 1 commit into
mainfrom
feat/prod-compile-check

Conversation

@nkg

@nkg nkg commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Supports HordiaLabs/scraper-control#71.

The gap

A Mix release evaluates config/config.exs at build time with config_env() == :prod — a path the normal dev/test compile never touches. A missing config/prod.exs, a broken import_config, or code that only compiles under dev/test passes CI green and then fails when the image is built.

Found the hard way in scraper-control: its Docker image had never built successfully, not once. Every merge to main for months died on

** (File.Error) could not read file "/app/config/prod.exs": no such file or directory
ERROR: process "/bin/sh -c mix compile" did not complete successfully: exit code: 1

…while every PR was green.

The reason it survived so long is structural, not careless. The image build is gated if: github.ref == 'refs/heads/main' — a sound pattern that keeps PR-controlled Dockerfiles off self-hosted runners — so the failure always lands post-merge, on a diff that already passed review, in a job at the end of the run behind needs:. The single place it surfaced was the single place nobody was watching.

The check

MIX_ENV=prod mix compile, in the ci job, behind run-prod-compile. It would have caught that exact bug on the PR that introduced it, for a fraction of the cost of a full image build.

Design notes:

  • Not --warnings-as-errors. The question is whether the production build works, not warning hygiene, and a prod-only warning in a dependency shouldn't red-line a consumer.
  • ci job only, not test-db — redundant there, and that job exists to run the suite against services.
  • Cost is bounded. _build is already in the cache, so the extra _build/prod tree costs a full compile only when the lockfile changes.
  • Off by default, consistent with the other opt-in gates.

Why not just build the image on PRs

More coverage, but it puts PR-controlled FROM refs and build steps on the self-hosted fleet, which the main-only gating deliberately avoids, and it costs minutes rather than seconds. This catches the config/compile class — the one that actually bit — without that trade.

Verification

Confirmed against the real bug: at scraper-control's pre-fix commit, MIX_ENV=prod mix compile fails with the File.Error above; with config/prod.exs added it compiles, mix release succeeds, and the image builds and serves for the first time.

actionlint + shellcheck clean; YAML parses; step confirmed present in ci and absent from test-db.

🤖 Generated with Claude Code

… at PR time

A Mix release evaluates config/config.exs at BUILD time with
config_env() == :prod — a path the normal dev/test compile never exercises.
A missing config/prod.exs, a broken import_config, or code that only compiles
under dev/test therefore passes CI green and fails when the image is built.

Found the hard way in a consumer repo: its Docker image had never built, not
once. Every merge to main for months died on

    ** (File.Error) could not read file "/app/config/prod.exs"

while every PR went green. The image build is gated
`if: github.ref == 'refs/heads/main'` — a sound pattern, it keeps
PR-controlled Dockerfiles off the self-hosted runners — but the consequence is
that this class of failure always lands post-merge, on a diff that already
passed review, in a job sitting at the end of the run behind `needs:`. The one
place it surfaced was the one place nobody was looking.

`MIX_ENV=prod mix compile` closes that gap for a fraction of the cost of a
full image build, and would have caught this exact bug on the PR that
introduced it.

Deliberately not --warnings-as-errors: the question here is whether the
production build works, not warning hygiene, and a prod-only warning in a
dependency should not red-line a consumer. `_build` is already cached, so the
extra prod tree costs a full compile only when the lockfile changes.

Added to the `ci` job only, not `test-db` — the two would be redundant, and
test-db's job exists to run the suite against services.

Off by default, consistent with the other opt-in gates. actionlint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEMvdSY4vBpcbE2cP1MP7A
@nkg
nkg merged commit f16876b into main Aug 25, 2026
11 checks passed
@nkg
nkg deleted the feat/prod-compile-check branch August 25, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant