TL;DR
- Filenames can't carry metadata. Stop asking them to — push the data into the binary and the EAS build record instead.
- Every build needs 5 attributes: git SHA, environment, version + build number, who triggered it, and when.
- One convention that holds up:
<product>-<env>-v<semver>-b<buildnum>-<sha7>.<ext> -
appVersionSource: remote+autoIncrement: truemeans you never ship two builds numbered 47. - Release notes generate themselves from
git logbetween two SHAs — one source, three surfaces. - A Notion database with 6 columns beat Sheets, Airtable, Linear, and a
#buildsSlack channel.
By month four of any real mobile project, you have ~400 build artifacts scattered across EAS Build history, Slack DMs ("here's the beta build.ipa"), Google Drive folders titled builds temp DELETE LATER, and someone's Downloads folder. When QA asks "which build did we send to the pilot testers on Tuesday?", nobody knows. Not the PM, not the tech lead, not the person who literally sent it.
This is a workflow problem, not a tools problem. Every team I've watched solve it did four specific things. Every team still drowning skipped one of them.
The Finder problem: 400 artifacts, no naming convention
Here's a real Finder window from a team I audited last month:
MyApp-build.ipa
MyApp-build (1).ipa
MyApp-build-final.ipa
MyApp-build-final-USE-THIS.ipa
MyApp-preview-v3.ipa
MyApp-preview-v3-hotfix.ipa
app-release.aab
app-release (2).aab
MyApp_2026-06-14.ipa
None of these tell you: what commit, what env, what tester group, what release date, what build number ASC saw. To answer any of those, you have to open the EAS dashboard, cross-reference build ID, cross-reference commit SHA, and hope nobody force-pushed to the branch since.
The root cause is that filesystem filenames are being asked to carry metadata they can't hold. The fix is to stop trying and move the metadata upstream — to the build itself.
The 5 attributes every build must carry (and where to attach them)
Every artifact needs to answer:
- What code? — git SHA (short, 7 chars) + branch
- What environment? — dev / preview / production / hotfix
- What version? — semantic version + build number (they're different things)
- Who / what triggered it? — human name or CI job ID
- When? — timestamp in one timezone (pick one, stick with it — IST for us)
Where to attach them, in order of durability:
- In the binary itself (Info.plist / AndroidManifest) — survives everything, hardest to fake
-
In the EAS build metadata (
gitCommitHash,gitCommitMessage— EAS captures these automatically if you useappVersionSource: remote) - In the artifact filename when downloaded — mostly for humans skimming Finder
- In your team's tracker (Notion / Linear / Slack canvas) — the human-readable index
The mistake teams make: they only do #3 (filename) and skip #1, #2, #4. Filename gets truncated, renamed, forwarded — the data dies. Metadata in the binary lives forever.
Here's the EAS-side setup that makes #1 and #2 automatic:
{
"cli": {
"appVersionSource": "remote"
},
"build": {
"production": {
"autoIncrement": true,
"env": {
"APP_ENV": "production"
}
}
}
}
appVersionSource: remote tells EAS to manage build numbers server-side, so you never manually bump ios.buildNumber again and you never ship two builds with number 47. autoIncrement: true adds one on every build. APP_ENV gets baked into the binary and is readable at runtime — so the app can display its own env in a debug menu, which is worth an hour of QA time per week alone.
A naming + tagging scheme that survives 6 months
For the human-facing name (filenames, Slack shares, Notion titles), one convention that's held up across three teams:
<product>-<env>-v<semver>-b<buildnum>-<sha7>.<ext>
Examples:
rapidnative-preview-v2.4.1-b127-a3f9c81.ipa
rapidnative-production-v2.4.0-b125-d7e21bc.aab
Rules that make this work in practice:
- No spaces, no parens, no "final", no "USE-THIS". Ever.
- Env is one of exactly 4 values. dev / preview / production / hotfix. If you need a fifth, you don't — merge it into one of these.
- SHA is always 7 chars. Not 6, not 8. Consistency matters more than length.
- The tracker row uses the same string as the row's title. So Cmd-F in Notion finds the artifact instantly.
This is boring on purpose. Boring conventions are the ones that survive team turnover.
Release notes that write themselves from artifact metadata
Once every build carries git SHA + previous-build's SHA (EAS knows both), release notes generate themselves. A tiny GitHub Actions step that runs after eas build completes:
- name: Generate release notes
run: |
PREV_SHA=$(gh api repos/${{ github.repository }}/releases/latest --jq .tag_name || echo "HEAD~10")
CUR_SHA=${{ github.sha }}
echo "## Changes in build $CUR_SHA" > release-notes.md
git log --pretty=format:"- %s (%an)" "$PREV_SHA..$CUR_SHA" >> release-notes.md
echo "" >> release-notes.md
The output becomes the testInformation field when you eas submit to TestFlight (external testers see it) and doubles as the Slack post when the build lands. One source, three surfaces, zero manual writing. If you'd rather not maintain the workflow file yourself, letsdeploy.it ships this exact step — SHA diffing, note generation, submission — as part of the pipeline.
The design principle: any artifact metadata worth showing to a human should surface in at least two places automatically. If it lives in only one place, it will get lost or become stale.
The one Notion / Linear pattern that beat every other tool
I tried five variants of build tracking over two years: Google Sheets, Airtable, a Notion database, a Linear project view, and a #builds Slack channel.
The one that stuck is embarrassingly simple: a Notion database with 6 columns and a filter.
Columns: Artifact name (the string above), Env, Version, SHA, Triggered by, Status. Filter: Env = production view for stakeholders, Env = preview view for QA, Status = failed view for post-mortems. That's it. No custom formulas, no linked databases, no rollups.
Why it beat the others:
- Sheets — everyone can edit, so schema drifts. Someone adds a "Notes" column, someone else adds "Notes 2," convention dies in 3 weeks.
- Airtable — great, until the free plan cap. Then you're paying $20/user/month for something a Notion page does.
- Linear project view — Linear wants everything to be an issue with an assignee. Builds don't have assignees. Force-fitting them broke Linear's own workflows.
-
Slack
#buildschannel — worst for retrieval. Nobody scrolls back 6 weeks to find a build. Slack search is okay but rewards recency, not structure.
Notion database wins because it's schemaless-enough to start ugly and iterate, structured-enough to filter, and searchable enough that Cmd-F works.
One bonus: give the database a public read-only share link. Stakeholders stop DM'ing you "which build is prod on?" and start bookmarking the URL.
The meta-lesson
Build artifact organization is a design problem, not an engineering one. The engineering (naming, metadata, CI) is 20% of the value. The other 80% is picking one convention and enforcing it socially — nobody renames files, nobody ships without the tracker row, nobody sends builds via DM.
If you're setting this up for the first time, budget half a day for the convention and half a day for onboarding the team to it. If your team already has 400 unstructured artifacts, don't retroactively rename them — start clean from today's build. Sunk cost is real; back-labeling artifacts is not worth the hours.
What does your build artifact naming look like right now? Drop your convention in the comments — I'm still collecting variants, especially from teams running more than one app out of a monorepo.
Top comments (0)