Skip to content

iOS changelog + auto TestFlight "What to Test" notes - #6154

Merged
lawrencecchen merged 1 commit into
mainfrom
feat-ios-changelog
Jun 15, 2026
Merged

lawrencecchen merged 1 commit into
mainfrom
feat-ios-changelog

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

iOS TestFlight testers get an opaque MARKETING_VERSION (timestamp) build with no notes on every install and auto-update, so there's no way to tell what changed. This adds a per-build iOS changelog and wires it into the upload so every TestFlight build shows its "What to Test" notes.

The changelog system

ios/CHANGELOG.md is the single source of truth, newest version on top (mirrors the macOS CHANGELOG.md ## [X.Y.Z] - date shape so it could feed a docs page later). Each version entry has two blocks:

  • Internal (ship ios, the cmux beta group): terse, dev-facing, per-build, includes a dogfood-focus line.
  • External / founders (ship ios founders): a curated, user-facing summary, no jargon or PR numbers.

Seeded with the real shipped 1.0.2 and 1.0.3 history (composer, View as Text, minimal QR, presence-gated + deep-linking notifications with a synced unread badge, offline sign-out, workspace-list polish, the render-grid watchdog fix), attributed from the merged PR list.

The top entry's version must equal the checked-in MARKETING_VERSION in ios/Config/Shared.xcconfig; this PR bumps it to 1.0.3 so the two agree.

How "What to Test" gets set (the ASC API call + when)

After a successful altool upload, upload-testflight.sh runs set-testflight-notes.sh, which awk-extracts the top entry's chosen block and calls asc_set_testflight_notes.py. That helper is dependency-free (ES256 JWT via openssl, same pattern as asc_max_build.py): it mints the JWT from the ASC API key (ios/Config/AppStoreConnect.local.plist, key id 4WN4S8ANN4), resolves the app by bundle id, finds the build by CFBundleVersion, then POST/PATCHes the en-US betaBuildLocalizations.whatsNew. A just-uploaded build isn't addressable until App Store Connect ingests it, so the build lookup polls (default 900s / 20s).

Audience is driven by the lane: internal cut -> Internal block; --external -> External block. --skip-notes (or CMUX_TESTFLIGHT_SKIP_NOTES=1) opts out; cloud-testflight.sh passes --external / --skip-notes through.

Fatal vs non-fatal split (from review): the deterministic local preconditions (changelog present, audience block non-empty, top version == the archived CFBundleShortVersionString) are checked before the archive and again right after it, and fail the upload. Only the App Store Connect mutation itself is non-fatal after upload (the binary is already on TestFlight; transient processing/API errors just warn, and notes can be re-applied with set-testflight-notes.sh --build-number <n> --audience <a>).

Internal vs external presentation design

Documented in docs/ios-release-notes.md: the version scheme testers see (MARKETING_VERSION (CURRENT_PROJECT_VERSION), the parens being the UTC build timestamp), and why there are two notes blocks per entry.

Phase 2 (spec only, deferred)

docs/ios-release-notes.md specs an in-app "What's New" sheet on first launch after an update for external/founders builds, sourced from the External block: a UserDefaults last-seen-version gate, the changelog parsed into a bundled resource at build time, a localized SwiftUI sheet, and a Settings re-open entry. That is iOS app runtime code (needs a tagged dogfood build + approval), so it's intentionally out of this PR to keep it scripts + docs only.

How to cut a release with notes going forward

  1. Edit ios/CHANGELOG.md: add the new version as the top entry (Internal + External blocks) and bump ios/scripts/bump-ios-version.sh X.Y.Z to match. Never cut a beta without a changelog entry.
  2. ship ios (internal) or ship ios founders (external). The upload sets the matching "What to Test" block automatically.
  3. If the notes step warns that the build is still processing, re-run ios/scripts/set-testflight-notes.sh --build-number <shipped> --audience internal|external.

The hq cmux-ios-release skill is updated with this step (not in this repo).

Verification

  • shellcheck clean on all three bash scripts; py_compile clean; bash -n clean.
  • The notes helper was exercised against the live ASC API in read mode: app resolution (dev.cmux.app.beta), build lookup by version, and existing en-US localization lookup all returned real IDs without mutating any build.
  • --validate-only verified: passes valid input (with/without version, no creds/build-number), fails on version mismatch and missing changelog.
  • autoreview clean (Codex), cmux-policy clean.

This is workflow/docs/script-only (no app/runtime behavior change), so it can merge after CI green with no dogfood per repo policy.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Scripts and docs only; no iOS app runtime changes. Release workflow now depends on changelog/version alignment and ASC API for notes (upload still succeeds if notes fail).

Overview
Adds ios/CHANGELOG.md as the per-build source of truth (Internal vs External blocks per audience) and bumps MARKETING_VERSION to 1.0.3 so the top entry matches Shared.xcconfig.

After a successful TestFlight upload, upload-testflight.sh now pushes the top changelog block to App Store Connect What to Test via new set-testflight-notes.sh (awk extract) and asc_set_testflight_notes.py (dependency-free ASC JWT + betaBuildLocalizations.whatsNew, with build-ingest polling). --skip-notes / CMUX_TESTFLIGHT_SKIP_NOTES opt out; cloud-testflight.sh forwards --skip-notes. Internal cuts use the Internal block; --external uses External.

Fatal pre-upload: changelog present, non-empty audience block, and top version equals the archive marketing version (--validate-only before archive and again after). Non-fatal post-upload: ASC mutation failures warn only; notes can be re-applied with set-testflight-notes.sh.

docs/ios-release-notes.md documents the workflow and defers in-app What's New (phase 2) to a follow-up.

Reviewed by Cursor Bugbot for commit a057592. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds a per-build iOS changelog and auto-publishes it to TestFlight “What to Test” so testers see what changed on install and auto-update. No runtime app changes.

  • New Features
    • ios/CHANGELOG.md is the single source of truth; each top entry has “Internal” and “External” blocks.
    • Upload sets TestFlight notes from the top entry via the ASC API (asc_set_testflight_notes.py), driven by lane: ship ios → Internal, --external → External; --skip-notes to opt out.
    • Preflight validation in upload-testflight.sh fails fast on missing/empty notes or version mismatch; post-upload notes are non-fatal and can be re-applied with ios/scripts/set-testflight-notes.sh.
    • cloud-testflight.sh passes --external/--skip-notes through; MARKETING_VERSION bumped to 1.0.3; added docs/ios-release-notes.md.

Written for commit a057592. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Documentation

    • Added comprehensive iOS release notes documentation outlining TestFlight delivery processes and planned in-app "What's New" sheet feature.
  • Chores

    • Updated iOS app version to 1.0.3.
    • Established structured changelog as the single source of truth for TestFlight release notes.
    • Enhanced release automation tooling to streamline TestFlight notes publication workflow.

Testers got an opaque MARKETING_VERSION (timestamp) build with no notes on every
install and auto-update. Add a per-build changelog and wire it into the upload so
each TestFlight build shows what changed.

- ios/CHANGELOG.md: single source of truth, newest version on top. Each entry has
  an Internal block (terse, dev-facing, per-build) and an External block (curated,
  user-facing). Seeded with the real 1.0.2 and 1.0.3 shipped history.
- ios/scripts/asc_set_testflight_notes.py: dependency-free ASC API helper (ES256
  JWT via openssl, mirrors asc_max_build.py). Resolves the app, finds the build by
  CFBundleVersion (polls while it processes), creates/PATCHes the en-US
  betaBuildLocalizations.whatsNew.
- ios/scripts/set-testflight-notes.sh: extracts the top entry's Internal/External
  block and calls the helper. --validate-only checks local preconditions with no
  network; --expect-marketing-version refuses to attach notes for the wrong version.
- upload-testflight.sh: preflights the notes (changelog present, version match)
  BEFORE the archive (fatal, deterministic), then sets the notes after a successful
  upload (non-fatal; the binary is already up, notes can be re-applied). Internal
  lane -> Internal block, --external -> External block. --skip-notes opts out.
- cloud-testflight.sh: passes --external / --skip-notes through.
- Shared.xcconfig: bump MARKETING_VERSION to 1.0.3 so the default lane matches the
  changelog top entry (the upload guard requires they agree).
- docs/ios-release-notes.md: presentation design (version scheme, internal vs
  external) and the phase-2 in-app What's New sheet spec (deferred).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 15, 2026 9:57am
cmux-staging Building Building Preview, Comment Jun 15, 2026 9:57am

@coderabbitai

coderabbitai Bot commented Jun 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5950de9b-3cd0-4e0b-8255-a926b27e9f9a

📥 Commits

Reviewing files that changed from the base of the PR and between cf04e44 and a057592.

📒 Files selected for processing (7)
  • docs/ios-release-notes.md
  • ios/CHANGELOG.md
  • ios/Config/Shared.xcconfig
  • ios/scripts/asc_set_testflight_notes.py
  • ios/scripts/cloud-testflight.sh
  • ios/scripts/set-testflight-notes.sh
  • ios/scripts/upload-testflight.sh

📝 Walkthrough

Walkthrough

Adds end-to-end automation for iOS TestFlight "What to Test" notes. A new ios/CHANGELOG.md serves as the source of truth. A Python script authenticates to App Store Connect via ES256 JWT and sets betaBuildLocalizations. A bash wrapper extracts notes by audience, validates version alignment, and delegates to the Python script. The upload pipeline gains a --skip-notes flag. MARKETING_VERSION is bumped to 1.0.3.

Changes

iOS TestFlight Release Notes Automation

Layer / File(s) Summary
CHANGELOG source of truth and version bump
ios/CHANGELOG.md, ios/Config/Shared.xcconfig
Introduces ios/CHANGELOG.md with contribution rules, tester conventions, and release entries for 1.0.3 and 1.0.2 with Internal/External sections. Bumps MARKETING_VERSION from 1.0.0 to 1.0.3 to match the top changelog entry.
App Store Connect API client
ios/scripts/asc_set_testflight_notes.py
New Python script implementing ES256 JWT auth (DER-to-JOSE r||s via openssl dgst), authenticated HTTP helpers, app-id resolution, build polling with timeout and exit-code 3 semantics, and betaBuildLocalizations PATCH/POST mutation.
Changelog extraction and validation wrapper
ios/scripts/set-testflight-notes.sh
New bash script that parses the top ios/CHANGELOG.md entry, checks it against --expect-marketing-version, extracts Internal/External bullets via awk, resolves ASC API credentials from env or .local.plist, supports --validate-only mode, and delegates to asc_set_testflight_notes.py.
--skip-notes wiring through the upload pipeline
ios/scripts/upload-testflight.sh, ios/scripts/cloud-testflight.sh
Threads --skip-notes / CMUX_TESTFLIGHT_SKIP_NOTES=1 through both scripts: adds SKIP_NOTES and NOTES_AUDIENCE variables, adds a validate-only preflight before archiving, enforces a fatal version-match guard after archive creation, and makes the post-upload notes mutation conditional and non-fatal.
Release notes process documentation
docs/ios-release-notes.md
Documents the shipped TestFlight notes pipeline, a Phase 2 in-app "What's New" sheet specification, and a step-by-step release-cut checklist.

Sequence Diagram

sequenceDiagram
  actor Developer
  participant upload_sh as upload-testflight.sh
  participant set_notes_sh as set-testflight-notes.sh
  participant asc_py as asc_set_testflight_notes.py
  participant ASC as App Store Connect API

  Developer->>upload_sh: run (--external / --skip-notes)
  upload_sh->>set_notes_sh: --validate-only --expect-marketing-version (preflight)
  set_notes_sh-->>upload_sh: notes preview or version mismatch error
  upload_sh->>upload_sh: xcodebuild archive + export + altool upload
  upload_sh->>set_notes_sh: --audience internal|external --build-number
  set_notes_sh->>asc_py: python3 asc_set_testflight_notes.py --bundle-id --build-number --notes-file
  asc_py->>ASC: GET /apps, GET /builds (poll)
  asc_py->>ASC: PATCH or POST betaBuildLocalizations.whatsNew
  ASC-->>asc_py: 200/201
  asc_py-->>set_notes_sh: exit 0
  set_notes_sh-->>upload_sh: notes set (or warning on failure)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • manaflow-ai/cmux#5592: Both PRs modify MARKETING_VERSION in ios/Config/Shared.xcconfig; this PR's notes pipeline version-matching guard depends on that same field.

Poem

🐰 Hop, hop! The changelog speaks,
Each bullet note a tester seeks.
JWT signed with openssl's art,
The build polls 'til it plays its part.
whatsNew is PATCHed, the testers cheer—
Release notes land with every build, my dear! 🌿


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux User-Facing Error Privacy ❌ Error PR violates user-facing-errors.md by exposing environment variable names (ASC_API_KEY_ID, ASC_API_ISSUER_ID, ASC_API_KEY_PATH, ASC_API_KEY_P8_BASE64) and internal config paths (ios/Config/AppStoreC... Sanitize error messages: replace environment variable names with generic guidance like "configure App Store Connect credentials" and remove internal file paths from user-facing output.
Cmux Full Internationalization ❌ Error The PR adds user-facing changelog text displayed in TestFlight "What to Test" notes that is English-only, violating the i18n rule requiring changelog copy to include translations for all supported... Add Japanese (ja) translations for ios/CHANGELOG.md External/Internal blocks and update scripts to support locale-aware CHANGELOG content for all app-supported locales, or defer changelog user-facing text to Phase 2.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding an iOS changelog system and automating TestFlight 'What to Test' notes, which is the primary objective of this PR.
Description check ✅ Passed The PR description covers all required template sections: detailed explanation of why the change is needed, how it works, testing verification, and implementation details. However, it lacks explicit checklist completion marks and a demo video section (though the latter is N/A for script-only changes).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PR contains no Swift source code changes—only docs, build config, and scripts. Actor isolation check is inapplicable per rule scope ("For production Swift changes").
Cmux Swift Blocking Runtime ✅ Passed PR contains no Swift code changes. All modifications are shell scripts, Python build tools, documentation, and build config—no production app runtime changes.
Cmux Expensive Synchronous Load ✅ Passed PR contains only documentation, build configuration, and build scripts; no Swift code changes or main-thread synchronous operations are present. Check not applicable.
Cmux Cache Substitution Correctness ✅ Passed The check applies only to production Swift/TypeScript/JavaScript code. This PR contains only documentation, build config, and build scripts (Python/Bash). Phase 2 runtime code using UserDefaults is...
Cmux No Hacky Sleeps ✅ Passed The polling in asc_set_testflight_notes.py is a designed, bounded-deadline timeout abstraction for handling documented App Store Connect API ingestion latency—part of the product behavior—not a rac...
Cmux Algorithmic Complexity ✅ Passed The PR adds iOS TestFlight release notes infrastructure (scripts and documentation) that operates only on single app/build metadata with bounded result sets (API limits ≤50), single changelog docum...
Cmux Swift Concurrency ✅ Passed PR contains no Swift code changes—only documentation, build config, Python, and bash scripts. Check is not applicable.
Cmux Swift @Concurrent ✅ Passed This PR contains no Swift code changes—only documentation, Bash scripts, Python scripts, and build configuration. The check is not applicable.
Cmux Swift File And Package Boundaries ✅ Passed PR contains no Swift files—only bash/Python scripts, markdown docs, and xcconfig. Swift file/package boundary rules don't apply.
Cmux Swift Logging ✅ Passed PR contains only scripts, docs, and config—no Swift app/runtime code. Print statements in CLI utility script (asc_set_testflight_notes.py) are legitimate CLI output, explicitly exempt per check rules.
Cmux Swiftui State Layout ✅ Passed PR modifies only Markdown docs, build config, Python scripts, and Bash scripts—zero Swift/SwiftUI changes. The SwiftUI state layout check does not apply to non-SwiftUI code.
Cmux Architecture Rethink ✅ Passed PR contains no Swift code changes—only docs, scripts, and build config. Swift architectural rethink rules do not apply to non-Swift changes.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains no Swift code changes; only documentation (.md), configuration (.xcconfig), Python (.py), and bash scripts (.sh). The auxiliary window close shortcuts check is inapplicable.
Cmux Source Artifacts ✅ Passed All 7 changed files are hand-written source (scripts, docs, config, changelog), not artifacts. No local tool output, generated logs, temp folders, caches, build output, or prohibited artifact direc...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-changelog

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a057592. Configure here.

--build-number "$SHIPPED_BUILD_NUMBER" \
--audience "$NOTES_AUDIENCE" \
--bundle-id "$PRODUCT_BUNDLE_IDENTIFIER" \
"${NOTES_VERSION_ARGS[@]}"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty array aborts notes step

Medium Severity

After upload, the script passes optional "${NOTES_VERSION_ARGS[@]}" into set-testflight-notes.sh. When the archived marketing version does not match the semver regex, NOTES_VERSION_ARGS stays empty. With set -u, expanding an empty array that way triggers an unbound-variable error on macOS Bash 3.2, so the post-upload notes step can fail even though TestFlight upload already succeeded.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a057592. Configure here.

if [[ ! -s "$NOTES_FILE" ]]; then
TOP_VER="$(grep -m1 '^## \[' "$CHANGELOG" || true)"
echo "error: no '### ${AUDIENCE^}' bullet items found in the top entry ($TOP_VER) of $CHANGELOG" >&2
exit 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bash 3.2 breaks audience error

Low Severity

When changelog extraction yields no bullets, the error path uses ${AUDIENCE^} to capitalize the audience name. That parameter expansion requires Bash 4+, but macOS still ships Bash 3.2 for #!/usr/bin/env bash, so validation can fail with a bad substitution instead of the intended message about missing ### Internal or ### External items.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a057592. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a per-build iOS changelog (ios/CHANGELOG.md) and wires it into the TestFlight upload pipeline so every build automatically gets "What to Test" notes via the App Store Connect API, replacing the opaque MARKETING_VERSION (timestamp) that testers previously saw. It is scripts + docs only — no app runtime changes.

  • set-testflight-notes.sh extracts the requested audience block (Internal/External) from the top changelog entry and invokes asc_set_testflight_notes.py, a dependency-free Python helper that mints an ES256 JWT and calls the ASC betaBuildLocalizations endpoint.
  • upload-testflight.sh gains two validation passes: a pre-archive validate-only check for changelog presence/audience block, and a post-archive marketing-version guard; the actual ASC mutation is intentionally non-fatal (the binary is already uploaded and notes can be re-applied later).
  • MARKETING_VERSION in Shared.xcconfig is bumped from 1.0.0 to 1.0.3 to match the top changelog entry and actual shipped version history.

Confidence Score: 4/5

Safe to merge after fixing the bash 3.2 incompatibility; all other script logic and guards are sound.

The shell scripts and Python helper are well-structured with solid pre/post-upload guards and a non-fatal design for the ASC mutation. The one concrete defect is in the error path of set-testflight-notes.sh: ${AUDIENCE^} is a bash 4+ expansion that dies with 'bad substitution' on macOS's system bash 3.2, turning a clear changelog-validation failure into a confusing one. The upload pipeline's validate-only calls pass through this code path, so it can surface during a normal release cut on a stock macOS or CI machine that relies on the system bash.

ios/scripts/set-testflight-notes.sh — line 144 uses ${AUDIENCE^} which requires bash 4+.

Important Files Changed

Filename Overview
ios/scripts/set-testflight-notes.sh New script to extract changelog blocks and push them as TestFlight notes; contains a bash 4+ ${AUDIENCE^} expansion at line 144 that fails on macOS's default bash 3.2.
ios/scripts/asc_set_testflight_notes.py New dependency-free Python helper that mints ES256 JWTs and calls the ASC API to set betaBuildLocalizations; polling loop, DER parsing, temp-file handling, and truncation guard all look correct.
ios/scripts/upload-testflight.sh Adds pre-archive validate-only preflight, post-archive marketing-version guard, and non-fatal post-upload notes mutation; NOTES_AUDIENCE is set after arg parsing so EXTERNAL_TESTING is already resolved.
ios/scripts/cloud-testflight.sh Adds --skip-notes flag and passes it through to upload-testflight.sh; change is minimal and correct.
ios/CHANGELOG.md New changelog file seeded with 1.0.2 and 1.0.3 history; top entry version (1.0.3) matches the bumped MARKETING_VERSION in Shared.xcconfig.
ios/Config/Shared.xcconfig Bumps MARKETING_VERSION from 1.0.0 to 1.0.3 to sync with the top changelog entry and actual shipped version history.
docs/ios-release-notes.md Operator doc describing the TestFlight notes mechanism and a phase-2 spec for an in-app What's New sheet; informational only.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant upload as upload-testflight.sh
    participant setnotes as set-testflight-notes.sh
    participant py as asc_set_testflight_notes.py
    participant ASC as App Store Connect API

    Dev->>upload: ship ios [--external] [--skip-notes]
    upload->>setnotes: --validate-only --audience a
    setnotes-->>upload: OK / error (fast fail, no network)

    upload->>upload: xcodebuild archive
    upload->>setnotes: --validate-only --audience a --expect-marketing-version X.Y.Z
    setnotes-->>upload: OK / error (version-match guard)

    upload->>ASC: altool upload IPA
    ASC-->>upload: upload accepted

    alt "SKIP_NOTES=0 and ASC API key present"
        upload->>setnotes: --build-number N --audience a --expect-marketing-version X.Y.Z
        setnotes->>py: --bundle-id ... --build-number N --notes-file /tmp/notes.txt
        loop Poll until build visible up to 900s / 20s interval
            py->>ASC: "GET /v1/builds?filter[version]=N"
            ASC-->>py: build id or empty (still processing)
        end
        py->>ASC: "GET /v1/builds/{id}/betaBuildLocalizations"
        ASC-->>py: existing en-US localization id or none
        py->>ASC: PATCH or POST betaBuildLocalizations whatsNew
        ASC-->>py: 200/201
        py-->>setnotes: exit 0
        setnotes-->>upload: notes set
    else "SKIP_NOTES=1 or no ASC key"
        upload->>upload: warn and skip (non-fatal)
    end
Loading

Reviews (1): Last reviewed commit: "iOS changelog + auto TestFlight "What to..." | Re-trigger Greptile


if [[ ! -s "$NOTES_FILE" ]]; then
TOP_VER="$(grep -m1 '^## \[' "$CHANGELOG" || true)"
echo "error: no '### ${AUDIENCE^}' bullet items found in the top entry ($TOP_VER) of $CHANGELOG" >&2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 ${AUDIENCE^} is a bash 4.0+ case-modification expansion. macOS ships bash 3.2.x as the system default (Apache license reasons), so on any stock macOS machine or macOS CI runner where Homebrew's bash is not at the front of PATH, this error path will die with bad substitution instead of emitting the intended helpful message. The preflight validate-only call made from upload-testflight.sh also triggers this code path on a malformed changelog, turning a clear failure into a confusing one.

Suggested change
echo "error: no '### ${AUDIENCE^}' bullet items found in the top entry ($TOP_VER) of $CHANGELOG" >&2
AUDIENCE_CAP="$(printf '%s' "${AUDIENCE:0:1}" | tr '[:lower:]' '[:upper:]')${AUDIENCE:1}"
echo "error: no '### ${AUDIENCE_CAP}' bullet items found in the top entry ($TOP_VER) of $CHANGELOG" >&2

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a057592466

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

data = body.get("data", [])
if not data:
return None
return data[0]["id"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for processing before returning a build

For a just-uploaded build that App Store Connect has listed but whose processingState is still PROCESSING, this returns the id immediately and the caller tries to create/patch betaBuildLocalizations right away. Those TestFlight metadata updates can still be rejected until processing finishes; because upload-testflight.sh treats any notes failure as non-fatal, the build can ship without the intended What to Test text even though it would have become writable within the timeout. Use the processingState value already requested here, or retry the localization mutation, before considering the build ready.

Useful? React with 👍 / 👎.

This branch was successfully deployed

1 active deployment
Preview – cmux — a0575924 Deployed Jun 15, 2026 by vercel[bot]
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