App Ops Desk · Issue #1

Versioning, build numbers, and release-train chaos

The quiet half-day killer: wrong build number, stale marketing version, TestFlight train confusion, and “I thought that archive was the one.”

Read time: 6 min Pairs with Issue #0 preflight Solo iOS/macOS founders
One action this week: Before you archive, write one line in a release log: marketing version → build number → git SHA → Xcode version → destination (TF / App Store). If you can’t fill it, you’re not ready to upload.

The real problem

Most solo founders don’t fail versioning because they don’t know what a build number is. They fail because the system has four sources of truth and none of them are automatically aligned: Xcode target settings, CI, the last TestFlight build, and your memory.

Rule set that prevents 80% of pain

  1. Marketing version (CFBundleShortVersionString) changes when users should notice a release family: 1.4.0 → 1.5.0.
  2. Build number (CFBundleVersion) must always increase for App Store Connect uploads in that app record.
  3. Never reuse a build number, even for a “tiny fix,” once it has been uploaded.
  4. One archive = one logged identity. If you rebuild, bump build number again.
  5. Destination is part of identity. “TF only” and “ready for App Store” are different mental states.

Failure mode A — “I’ll just reupload the same build number”

Symptom: Connect rejects the upload or you can’t tell which binary QA actually tested.

Reusing build numbers creates detective work. Your future self will not remember whether build 188 included the paywall fix. If the bits changed, the build number changes. No exceptions for solo projects.

Failure mode B — marketing version drift

Symptom: App Store page, TestFlight label, and Xcode disagree. Support screenshots become ambiguous.
Preflight: Decide the user-facing version first, then set Xcode, then archive. Don’t invent the number during Organizer export.

Failure mode C — TestFlight train spaghetti

Symptom: Testers install an older build while you’re debugging a newer one. Feedback becomes nonsense.

Solo founders often keep multiple live builds because deleting/expiring feels final. That’s fine — if communication is explicit. Put the build number in every tester note. “Latest” is not a version.

Template tester note:
Build 214 (1.5.0)
What to test: onboarding → settings export
Known issue: iPad landscape spacing
Previous build 211 is obsolete for this test

Failure mode D — CI and local Xcode disagree

Symptom: Local archive is 1.5.0 (220), CI produces 1.4.2 (198), and both get discussed as “the release.”

Pick one release captain path. If you archive locally, CI is not the authority that day. If CI is the authority, don’t also drag random local archives into Connect.

Minimal release log (steal this)

Date:
Marketing version:
Build number:
Git SHA:
Xcode:
Destination: TestFlight / App Store
Why this build exists:
Smoke checks passed:
Upload result:
Notes:

Copy/paste versioning preflight

  1. Marketing version chosen on purpose (not leftover from last feature branch).
  2. Build number higher than every build already in App Store Connect.
  3. Git working tree clean enough that the SHA means something.
  4. Release log line written before archive.
  5. Tester notes drafted with exact build number.
  6. You know whether this build is allowed to go beyond TestFlight.

How this fits App Ops Desk

Issue #0 was about hard blockers (toolchain, dSYMs, false TestFlight confidence). Issue #1 is about identity: knowing which bits you actually shipped. Next issues will cover privacy answers, metadata freezes, and rollback discipline.

Join the App Ops Desk waitlist Founding rate: $19/mo for the first 20. Free samples stay public.