Discussing how to store Hermes configuration as a single source of truth so builds are reproducible, validate build artifacts in CI, and integrate linting and the release pipeline so every release passes the same checks.

Episode 10 closed the observability chain: source maps, readable stack traces, and tidy telemetry. Now we step back to the side nobody sees: how to make sure every build produces the same bytecode, validated, and ready to release. Without this, even a good source map becomes a lie if the build can't be reproduced.
The roadmap for episode 11: storing Hermes configuration for build reproducibility, validating build output with CI checks, then integrating everything with linting and release pipelines.
The classic team problem: developer A builds with the -O flag, developer B forgets it, and the bytecode produced differs on their machines. The solution is simple — Hermes configuration must be committed code, not memorized commands. In React Native, compiler flags are stored in build.gradle:
react {
hermesFlags = [
"-O",
"-output-source-map",
]
}For embeddable workloads outside React Native, store the compile command in a shared script, for example scripts/compile.sh. Never run hermesc ad hoc without documenting the flags. If the configuration changes, that change must be visible in code review like any other code change.
This single source of truth is what makes builds reproducible: as long as everyone uses the same file, the bytecode produced is identical.
Reproducibility isn't enough with just the same flags. Toolchain versions also have to be locked:
hermesc commit for custom embedding. Bytecode is locked to the engine version — remember the lesson from episode 13 later.bun.lock or package-lock.json must be in the repo, and CI installs use frozen mode, for example bun install --frozen-lockfile.hermesc -version
node --version
bun --versionRun the three commands above when CI starts, then record the output as part of the build report. When a bug appears, you can distinguish "a problem in the code" from "a problem in the toolchain" just from this record.
Info
Frozen lockfile mode is the first gate of reproducibility: if a dependency isn't recorded, the install fails immediately instead of silently producing different bytecode.
A successful build doesn't necessarily mean the output is correct. CI must validate artifacts before they're considered shippable. Minimum checks:
hbcdump.A validation script can be as simple as this:
set -e
test -s dist/index.android.bundle
test -s dist/index.android.map
hbcdump -summary dist/index.android.bundleIf any check fails, the script exits with a non-zero code and CI goes red. Building automated checks like this is much cheaper than finding a broken bundle in the hands of users.
Integration happens in the workflow. Here's an example of the steps commonly used in a Hermes build pipeline:
name: hermes-build-check
on:
pull_request:
push:
branches: [main, staging]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build bundle
run: bun run build:content
- name: Validate artifacts
run: bash scripts/validate-artifacts.sh
- name: Upload source maps
run: bash scripts/upload-sourcemaps.shNotice that every pull request already goes through the same flow as a release: locked install, build, validation, and source map upload. This gives the release pipeline no surprises — the kind that usually only show up at the last step before shipping.
Build configuration also needs to be maintained through linting. Some proven practices:
hermesFlags or a missing source map on release builds.rc tag or stable version can be promoted.A good release pipeline is deterministic: the same sequence runs, the same artifacts are produced, and the go/no-go decision is made by CI, not by human memory.
With configuration stored as code, a locked toolchain, and CI validating every artifact, you have a build foundation that's repeatable and trustworthy. Linting and release gates close the loop: nothing ships without passing the same checks.
The essentials to take home:
In episode 12 we enter a new phase: Secure Runtime Practices. You'll learn to load JavaScript bundles securely, avoid remote code execution and unsafe eval, and practical mitigations for mobile and edge apps. See you there!