iOS build fails on CI with 'missing classname for isa key' error but works fine locally

I have an iOS app that builds perfectly on my local machine but keeps failing when I try to build it using continuous integration. The error happens during the package dependency resolution phase and shows this message:

xcodebuild: error: Cannot read project 'MyApp.xcodeproj' from directory '/Users/runner/work/MyApp-iOS/MyApp-iOS/MyApp'.
Reason: The project 'MyApp' is corrupted and cannot be opened. Check the project file for invalid changes or merge conflicts.
Exception: missing classname for isa key

I already verified that the project file is valid by running plutil -lint MyApp/MyApp.xcodeproj/project.pbxproj and it shows no issues. This is really puzzling because the exact same code works without problems on my development machine.

Here’s my CI configuration:

name: "iOS Build Pipeline"

on:
  push:
    branches:
      - main

jobs:
  deploy:
    name: Build and Test iOS App
    runs-on: macos-13
    steps:
      - name: Get Source Code
        uses: actions/checkout@v4

      - name: Configure Xcode
        run: sudo xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer
        
      - name: Setup Ruby Environment
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'

      - name: Install Dependencies
        run: |
          gem install bundler
          bundle install

      - name: Setup Build Keychain
        run: |
          security create-keychain -p "temp_pass" ci.keychain
          security default-keychain -s ci.keychain
          security unlock-keychain -p "temp_pass" ci.keychain

      - name: Import Certificates
        run: |
          brew install gnupg
          echo "${{ secrets.GPG_PRIVATE_KEY }}" > ~/cert-key.asc
          gpg --import ~/cert-key.asc
          
      - name: Verify Project Structure
        run: |
          find . -name "*.xcodeproj"
          plutil -lint MyApp/MyApp.xcodeproj/project.pbxproj
  
      - name: Execute Build
        env:
          CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }}
          API_KEY_ID: ${{ secrets.API_KEY_ID }}
          API_ISSUER_ID: ${{ secrets.API_ISSUER_ID }}
        run: fastlane build_app

Anyone experienced this issue before? What could cause this difference between local and CI environments?

This looks like file corruption during checkout, not an actual project issue. I hit something similar when our CI switched checkout action versions. The pbxproj file has references that get mangled when there’s an encoding mismatch between your local setup and the CI runner. Add fetch-depth: 0 to your checkout action and make sure you’re using the same Xcode version locally and in CI. Sometimes it’s invisible characters or encoding differences that plutil misses but Xcode’s parser catches. Also check if your local machine has any global Git hooks or configs that might be auto-cleaning the project file. You could add a verification step that dumps the actual content around the isa keys to see what’s different between environments.

Check if your local Ruby version matches CI - bundler can mess with xcodeproj files during dependency resolution when versions don’t align. I hit this exact issue with local Ruby 2.7 vs CI Ruby 3.0. Pin your Ruby version or throw a git status after bundle install to catch any unexpected changes.

Had this exact problem six months ago - drove me nuts for days. Your project structure is fine. The real issue is how CI handles checkout and file encoding. For me, it was Git’s line ending settings on the macOS runner. The project.pbxproj file has binary-like content that gets corrupted when Git tries to normalize line endings. Fixed it by adding a .gitattributes file to my repo root: *.pbxproj merge=union; *.pbxproj binary. The binary flag stops Git from messing with line endings in the project file. Once I committed this, CI builds worked every time. Also check if your local Git config differs from CI defaults, especially core.autocrlf and core.eol values.