feat(elixir): add run-prod-compile to catch production-build breakage at PR time - #69
Merged
Conversation
… 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supports HordiaLabs/scraper-control#71.
The gap
A Mix release evaluates
config/config.exsat build time withconfig_env() == :prod— a path the normal dev/test compile never touches. A missingconfig/prod.exs, a brokenimport_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 tomainfor months died on…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 behindneeds:. The single place it surfaced was the single place nobody was watching.The check
MIX_ENV=prod mix compile, in thecijob, behindrun-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:
--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.cijob only, nottest-db— redundant there, and that job exists to run the suite against services._buildis already in the cache, so the extra_build/prodtree costs a full compile only when the lockfile changes.Why not just build the image on PRs
More coverage, but it puts PR-controlled
FROMrefs 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 compilefails with theFile.Errorabove; withconfig/prod.exsadded it compiles,mix releasesucceeds, and the image builds and serves for the first time.actionlint+shellcheckclean; YAML parses; step confirmed present inciand absent fromtest-db.🤖 Generated with Claude Code