Skip to content

Unblock internal TestFlight: strip embedded static frameworks, fix assign jobs - #8235

Merged
azooz2003-bit merged 1 commit into
mainfrom
feat-internal-beta-unblock
Jul 16, 2026
Merged

azooz2003-bit merged 1 commit into
mainfrom
feat-internal-beta-unblock

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator

Every internal beta upload was rejected by App Store Connect during processing with ITMS-90208. Root cause: iroh-ffi's Iroh.framework binary is a static archive (ar), and Xcode embeds it into cmux.app/Frameworks/ anyway. ASC validates the embedded framework binary, which has no Mach-O minimum-OS load command, so the rejection fired regardless of deployment target — the 1.0.2-cmux.2 Info.plist pin and the 18.4→18.0→17.5 deployment-target changes were all aimed at the wrong layer. The external beta's history of good builds all predate the Iroh integration (#7908).

Fixes:

  • ios/scripts/upload-testflight.sh: the manual re-sign path strips embedded static-archive frameworks before signing (their code is already statically linked into the app executable; an otool -L gate proves nothing dynamically links them). verify_ipa_framework_minimum_os_versions now hard-fails on any embedded static archive, covering the automatic path too.
  • .github/workflows/ios-testflight.yml: the internal assign job passed both group id and name, which the assign script rejects (set only one of --group-id or --group-name) — every internal assignment failed in seconds. Only the id is passed now. The assign-external-group job is removed: it polled dev.cmux.app.beta for builds that upload to dev.cmux.app.internal, hanging 40 minutes per run. The decide job's assign-only retry now keys off the internal assignment job and its artifact.
  • ios/Config/Shared.xcconfig: restores IPHONEOS_DEPLOYMENT_TARGET = 18.4. The 17.5 lowering broke the build (run https://github.com/manaflow-ai/cmux/actions/runs/29469612881 failed: compiling for iOS 17.5, but module 'CMUXMobileCore' has a minimum deployment target of iOS 18.0).

Follow-up (not this PR): make manaflow-ai/iroh-ffi ship a dynamic framework or a plain static-library target so nothing needs stripping.

🤖 Generated with Claude Code


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


Summary by cubic

Unblocks internal TestFlight uploads by stripping embedded static frameworks and fixing the internal assignment flow, resolving ITMS-90208 and hung jobs. Restores the iOS deployment target to 18.4 to match package minimums.

  • Bug Fixes
    • ios/scripts/upload-testflight.sh: strip embedded static-archive frameworks before re-signing and hard-fail IPA verification if any remain (prevents ITMS-90208).
    • .github/workflows/ios-testflight.yml: pass only CMUX_TESTFLIGHT_INTERNAL_GROUP_ID to the assign script; remove the external assignment job that polled dev.cmux.app.beta and timed out; publish ios-testflight-assignment-state-complete so decide’s assign-only retry keys off the internal job.
    • ios/Config/Shared.xcconfig: set IPHONEOS_DEPLOYMENT_TARGET back to 18.4 to align with .iOS(.v18) Swift packages and fix build failures.

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

Review in cubic

Summary by CodeRabbit

  • New Features

    • Updated iOS TestFlight distribution to target the internal app and audience.
    • Improved assignment retry handling and status reporting.
  • Bug Fixes

    • Added validation to detect unsupported static-archive frameworks before upload.
    • Safely removes unused static-archive frameworks during manual signing.
    • Prevents uploads when required framework links are detected.
  • Maintenance

    • Raised the iOS deployment target to 18.4 for compatibility with current Swift packages.

…sign jobs

Three stacked failures kept every internal beta out of TestFlight:

1. ITMS-90208 processing rejection: iroh-ffi's Iroh.framework binary is a
   static archive, and Xcode embeds it into cmux.app/Frameworks/ anyway.
   ASC validates the embedded framework BINARY, which has no Mach-O
   minimum-OS load command, so every upload containing Iroh was rejected
   in processing regardless of deployment target or the framework's
   Info.plist. The re-sign path now strips embedded static-archive
   frameworks (their code is already statically linked into the app
   executable; a load-command gate proves it), and the IPA verifier
   hard-fails if one is still embedded.

2. Internal group assignment failed instantly: the workflow passed both
   CMUX_TESTFLIGHT_INTERNAL_GROUP_ID and _NAME, and the assign script
   errors when both are set. Only the id is passed now.

3. assign-external-group hung 40 minutes per run polling
   dev.cmux.app.beta for builds that now upload to dev.cmux.app.internal.
   Job removed; the decide job's assign-only retry logic now keys off the
   internal assignment job, and the internal job uploads the
   assignment-state artifact name decide checks.

Also restores IPHONEOS_DEPLOYMENT_TARGET to 18.4. The 18.0/17.5 lowering
chased the ITMS-90208 symptom at the wrong layer, and 17.5 broke the
build outright (packages declare .iOS(.v18); run #509 failed with
'compiling for iOS 17.5, but module CMUXMobileCore has a minimum
deployment target of iOS 18.0').

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88f33bc3-663f-4195-98bf-80896dfb6aac

📥 Commits

Reviewing files that changed from the base of the PR and between 1f385e6 and b8da97a.

📒 Files selected for processing (3)
  • .github/workflows/ios-testflight.yml
  • ios/Config/Shared.xcconfig
  • ios/scripts/upload-testflight.sh

📝 Walkthrough

Walkthrough

The PR narrows the iOS TestFlight workflow to the internal app and aligns assignment retry artifacts and inputs. It also raises the deployment target and adds static-archive framework validation and removal during manual IPA signing.

Changes

iOS TestFlight delivery

Layer / File(s) Summary
Internal TestFlight assignment flow
.github/workflows/ios-testflight.yml
Assignment retry detection accepts either assignment job, internal assignment receives only the group ID, the state artifact uses a generic name, and summaries/comments describe the internal lane.
IPA framework packaging validation
ios/Config/Shared.xcconfig, ios/scripts/upload-testflight.sh
The deployment target changes to iOS 18.4; static-archive frameworks are rejected or removed before manual re-signing when they are not dynamically referenced.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • manaflow-ai/cmux#8233 — Covers the deployment-target mismatch addressed by changing the target from iOS 17.5 to 18.4.

Possibly related PRs

  • manaflow-ai/cmux#8180 — Updates the same internal assignment flow, retry handling, artifact, and group ID wiring.
  • manaflow-ai/cmux#8131 — Modifies framework MinimumOSVersion handling in the same upload script.
  • manaflow-ai/cmux#8220 — Changes the same deployment target to address the ITMS-90208 compatibility issue.

Suggested reviewers: lawrencecchen

✨ 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-internal-beta-unblock

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.

@azooz2003-bit
azooz2003-bit merged commit 61d4956 into main Jul 16, 2026
5 of 6 checks passed
@greptile-apps

greptile-apps Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR unblocks internal TestFlight uploads that were failing at App Store Connect processing (ITMS-90208) and fixes silent CI failures in the assignment jobs. The root causes — an embedded static-archive Iroh.framework, a mutually-exclusive --group-id/--group-name argument error, and an external-group job polling the wrong bundle ID — are all addressed correctly.

  • Shell script (upload-testflight.sh): strips embedded static-archive frameworks before re-signing, gated by an otool -L check, and hard-fails verify_ipa_framework_minimum_os_versions if any slip through the automatic path.
  • Workflow (ios-testflight.yml): removes the broken assign-external-group job, fixes the internal assignment job to pass only CMUX_TESTFLIGHT_INTERNAL_GROUP_ID, renames the artifact to match what the decide job checks, and updates assign-job name search for backward-compatible retry detection.
  • Shared.xcconfig: restores IPHONEOS_DEPLOYMENT_TARGET = 18.4.

Confidence Score: 3/5

Safe to merge for unblocking TestFlight uploads, but the assign-only retry path for the internal group will silently no-op if assignment ever fails after a successful upload.

The shell script and xcconfig changes are solid and directly fix the ITMS-90208 rejection. The workflow changes correctly remove the polling-wrong-bundle-ID external job and fix the group ID/name mutual-exclusion bug. The one concrete gap: assign-internal-group lacks always() on its if condition, so scheduled runs that hit shouldAssignOnly=true skip the job entirely. A secondary assignment failure after a good upload would strand the build unassigned with no automated recovery.

.github/workflows/ios-testflight.yml — specifically the assign-internal-group job if condition and the two stale external labels in the decide job.

Important Files Changed

Filename Overview
.github/workflows/ios-testflight.yml Removes broken external-assign job, fixes internal-group env var collision, renames artifact — but assign-internal-group still lacks always() so the assign-only retry path silently skips
ios/Config/Shared.xcconfig Restores IPHONEOS_DEPLOYMENT_TARGET to 18.4 with clear comment explaining why the previous 17.5 lowering was wrong
ios/scripts/upload-testflight.sh Adds static-archive detection in verify function (hard fail) and strips embedded static-archive frameworks before re-signing, gated by an otool -L dynamic-link safety check

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[decide job] -->|should_build=true| B[upload job]
    A -->|should_assign_only=true| D
    B --> C{upload success?}
    C -->|yes| D[assign-internal-group]
    C -->|no| E[workflow fails]
    D --> F{assignment done?}
    F -->|yes| G[upload artifact:
ios-testflight-assignment-state-complete]
    F -->|no| H[workflow step fails
no artifact uploaded]
    G --> I[next decide run:
lastAssignmentSucceeded=true]
    H --> J[next decide run:
shouldAssignOnly=true]
    J --> K[assign-internal-group SKIPPED
missing always on if condition]
    style K fill:#ff9999
    style G fill:#99ff99
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[decide job] -->|should_build=true| B[upload job]
    A -->|should_assign_only=true| D
    B --> C{upload success?}
    C -->|yes| D[assign-internal-group]
    C -->|no| E[workflow fails]
    D --> F{assignment done?}
    F -->|yes| G[upload artifact:
ios-testflight-assignment-state-complete]
    F -->|no| H[workflow step fails
no artifact uploaded]
    G --> I[next decide run:
lastAssignmentSucceeded=true]
    H --> J[next decide run:
shouldAssignOnly=true]
    J --> K[assign-internal-group SKIPPED
missing always on if condition]
    style K fill:#ff9999
    style G fill:#99ff99
Loading

Comments Outside Diff (3)

  1. .github/workflows/ios-testflight.yml, line 617 (link)

    P1 assign-internal-group missing always() breaks the assign-only retry path

    In GitHub Actions, when a needed job is skipped, all dependent jobs are also skipped unless the if condition is prefixed with always(). The upload job is skipped whenever should_build == 'false', so any scheduled run where shouldAssignOnly == true will silently skip assign-internal-group too — the retry never executes. The assign-external-group job (now removed) had always() precisely for this reason. Without it here, if an upload succeeds but the assignment step fails, there is no automatic recovery path: every subsequent scheduled no-build run hits shouldAssignOnly = true but does nothing.

  2. .github/workflows/ios-testflight.yml, line 285-289 (link)

    P2 These labels still reference "external" but the lane now only uploads to dev.cmux.app.internal. A reader correlating CI summary output with these strings will be confused.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  3. .github/workflows/ios-testflight.yml, line 312 (link)

    P2 The summary table row label still says "external" assignment, which will be confusing in workflow run summaries now that only internal assignment exists.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "Unblock internal TestFlight: strip embed..." | Re-trigger Greptile

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