La maggior parte dei fallimenti CI/CD di Capacitor deriva sempre dalle stesse poche cause: signing iOS code, tool macOS mancanti o obsoleti, una build web che non è mai finita nel progetto nativo, build number riutilizzati e requisiti degli store che falliscono solo al momento dell'upload. Ognuna ha una causa nota e una correzione da applicare una volta sola. Questa guida raggruppa le insidie per fase della pipeline, con l'errore che vedrai, perché succede e cosa cambiare.
Se stai configurando una pipeline da zero, leggi Configurare CI/CD per app Capacitor prima, poi usa questa lista per rafforzarlo.
Fase 1: Build del layer web
Trappola: l'app nativa distribuisce una build web vecchia
Sintomo: CI va a buon fine, l'app si installa, ma mostra l'interfaccia di ieri.
Causa: cap sync copia ciò che si trova in webDir in quel momento. Se la pipeline esegue cap sync prima della web build, o la web build scrive in una cartella diversa da webDir nella capacitor.config.ts, il progetto nativo riceve file obsoleti.
Soluzione: esegui sempre i passaggi in questo ordine e fallisci se la cartella di output è vuota.
bun install --frozen-lockfile
bun run build
test -f dist/index.html || { echo "web build missing"; exit 1; }
bunx cap sync
Assicurati che webDir corrisponde all'output del bundler (dist per Vite, www per Angular con Ionic, build per alcune configurazioni React).
Trappola: URL del dev server lasciato nella configurazione
Sintomo: la release build mostra uno schermo vuoto o tenta di caricare http://192.168.x.x:5173.
Causa: server.url nella capacitor.config.ts era stato impostato per il live reload e committato.
Soluzione: non commettere mai server.url. Leggilo da una variabile d'ambiente che CI non imposta mai:
import type { CapacitorConfig } from '@capacitor/cli'
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
...(process.env.LIVE_RELOAD_URL && {
server: { url: process.env.LIVE_RELOAD_URL, cleartext: true },
}),
}
export default config
Trappola: variabili d'ambiente incorporate al momento sbagliato
Sintomo: l'app di produzione comunica con l'ambiente di staging API.
Causa: Vite, webpack e Angular incorporano le variabili d'ambiente al momento del build. Il valore presente quando bun run build ran è quello presente nel binario e in qualsiasi live update generato dallo stesso job.
Soluzione: Imposta le variabili specifiche dell'ambiente prima del web build, per job, e genera bundle separati per staging e produzione. Non provare a sostituirle in seguito. cap sync.
Errore comune: lockfile drift
Sintomo: la versione di un plugin in CI differisce da quella locale e la compilazione nativa fallisce per simboli mancanti.
Soluzione: fai il commit del lockfile e installa con bun install --frozen-lockfile (o npm ci). Fissa @capacitor/core, @capacitor/ios, @capacitor/android, e @capacitor/cli alla stessa versione. Le versioni non corrispondenti causano spesso errori nativi; vedi Fix Capacitor version mismatch errors.
Fase 2: La toolchain nativa
Errore comune: versione Node, JDK o Xcode errata
Sintomo: Unsupported class file major version, The engine "node" is incompatible, o errori Xcode relativi alle funzionalità di SDK.
Causa: le immagini dei runner hosted cambiano, e Capacitor 8 ha requisiti minimi rigidi.
| Strumento | Capacitor 8 requisiti |
|---|---|
| Node.js | 22 o superiore |
| JDK | 21 |
| Xcode | 26 o superiore |
| Target di deployment iOS | 15.0 |
Android minSdkVersion / targetSdkVersion |
24 / 36 |
Soluzione: fissa esplicitamente ogni versione nella pipeline invece di fidarti di latest:
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26'
Pitfall: fare build con un Xcode che Apple non accetta più
Sintomo: l'upload fallisce con un messaggio che indica che l'app è stata compilata con un SDK non supportato.
Causa: Dal 28 aprile 2026, App Store Connect richiede Xcode 26 e il SDK iOS 26. I Mac self-hosted e le immagini runner più vecchie usano ancora Xcode 16 di default.
Soluzione: seleziona un macos-26 immagine o installa Xcode 26 sui tuoi runner. Dettagli in Il requisito Xcode 26 di Apple per le app Capacitor.
Trappola: confusione tra CocoaPods e SPM
Sintomo: xcodebuild: error: 'App.xcworkspace' does not exist, o pods non trovati.
Causa: I nuovi Capacitor 8 progetti usano Swift Package Manager e build ios/App/App.xcodeproj. I progetti più vecchi usano CocoaPods e build ios/App/App.xcworkspace dopo pod install. Le pipeline copiate da tutorial datati presuppongono il workspace.
Soluzione: controlla quale usa il tuo progetto e genera il file corretto. Se stai migrando, vedi Come migrare la tua app Capacitor a SPM.
Problema: i build dei plugin Android si rompono dopo un aggiornamento di Gradle
Sintomo: Namespace not specified, package attribute is deprecated, o errori da un plugin build.gradle dopo aver aggiornato Android Studio.
Soluzione: fissa la versione dell'Android Gradle Plugin in android/build.gradle, aggiorna su un branch e aggiorna prima i plugin. Gli errori specifici sono trattati in Fix degli errori di build del plugin Capacitor con AGP 9.
Fase 3: firma Code
Errore comune: la firma iOS funziona solo sul tuo Mac
Sintomo: No signing certificate "iOS Distribution" found, No profiles for 'com.example.app' were found, o errSecInternalComponent.
Causa: il keychain del tuo Mac contiene il certificato e Xcode scarica i profili per te. Un runner CI non ha nessuno dei due e il suo keychain è bloccato in una sessione non interattiva.
Soluzione: importa il certificato in un keychain temporaneo sbloccato e installa il profilo dove Xcode 16 e versioni successive lo cercano:
security create-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security set-keychain-settings -lut 21600 ci.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security import dist.p12 -k ci.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" ci.keychain
security list-keychains -d user -s ci.keychain login.keychain
PROFILES="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
mkdir -p "$PROFILES"
cp app.mobileprovision "$PROFILES/"
Usa il nome dell'identity Apple Distribution, non quello legacy iOS Distribution. fastlane’s setup_ci più match automatizza tutto questo. Capgo Build prende il certificato e il profilo come variabili d'ambiente e gestisce il keychain sulle proprie macchine, così il job Linux non deve toccare security.
Trappola: certificati scaduti o revocati
Sintomo: una pipeline che funzionava da un anno fallisce improvvisamente.
Causa: I certificati di distribuzione Apple e i profili di provisioning scadono dopo un anno. Un collega che crea un nuovo certificato in Xcode può anche invalidare il profilo utilizzato dalla tua CI.
Soluzione: metti le date di scadenza nel calendario del team, mantieni un certificato di distribuzione condiviso per la CI e verifica prima di fare il build. bunx @capgo/cli@latest build prescan --platform ios verifica la scadenza del certificato, la password e l'abbinamento del profilo prima di caricare qualsiasi cosa.
Trappola: login Apple ID e prompt di autenticazione a due fattori
Sintomo: fastlane si blocca in attesa di un codice a 6 cifre code.
Soluzione: usa una chiave API di App Store Connect (.p8, key ID, issuer ID) invece di ID Apple e password. Non richiede l'autenticazione a due fattori e può essere limitata al ruolo App Manager. Se l'autenticazione fallisce ancora con una chiave corretta, controlla l'orologio del runner: il token è firmato con l'ora locale e Apple rifiuta i token con timestamp non allineati.
Trappola: segreti base64 che non vengono decodificati
Sintomo: MAC verification failed, invalid keystore format, o base64: invalid input.
Causa: a capo aggiunti durante la copia del valore base64, oppure un .p12 creato con i valori predefiniti di OpenSSL 3, non leggibili da macOS.
Soluzione: codifica su una riga e usa -legacy quando crei un .p12 con OpenSSL 3:
base64 -i dist.p12 | tr -d '\n' > dist.p12.b64
openssl pkcs12 -export -legacy -inkey key.pem -in cert.pem -out dist.p12
Trappola: keystore Android perso
Sintomo: non puoi firmare un aggiornamento perché nessuno ha il keystore.
Soluzione: con Play App Signing, il keystore è la chiave di upload e il supporto di Play Console può registrarne una nuova. Conserva il keystore nei segreti della tua CI e in un backup offline. Non lasciarlo mai solo su un laptop. Il Generatore di keystore per Android ne crea uno nuovo se parti da zero.
Fase 4: design della pipeline
Trappola: build native a ogni pull request
Sintomo: check PR lenti e una bolletta elevata per i minuti macOS.
Causa: Le build iOS su runner macOS hosted sono i minuti più costosi nella maggior parte dei piani CI, e la maggior parte dei commit tocca solo JavaScript.
Soluzione: esegui lint, test e la build web su ogni PR. Esegui le build native sui tag di release o sui merge in main. Per le anteprime PR, pubblica il bundle web su un canale Capgo invece di compilare un binario, come descritto in Confronto tra piattaforme CI/CD per app Capacitor.
Trappola: build di iOS e Android in sequenza
Soluzione: usa una matrix così entrambe le piattaforme buildano in parallelo, e imposta fail-fast: false così un problema di signing su iOS non annulla una build Android valida.
strategy:
fail-fast: false
matrix:
platform: [ios, android]
Trappola: nessuna cache, o quella sbagliata
Sintomo: ogni build scarica da zero le dipendenze Gradle e CocoaPods, oppure una build di staging porta config di produzione da una cache obsoleta.
Soluzione: cache ~/.gradle/caches, ~/.gradle/wrapper, e ios/App/Pods basata sui lockfile. Dividi le cache per ambiente. Con Capgo Build, la cache di build per app può essere divisa con --cache-key prod e --cache-key staging, o saltato con --no-cache per una build pulita.
Errore comune: percorsi monorepo
Sintomo: could not find capacitor.config o plugin mancanti nel progetto nativo.
Soluzione: esegui i comandi Capacitor dal pacchetto dell'app e punta gli strumenti ai file hoisted node_modules. Il Capgo CLI accetta --path e --node-modules per questo.
Fase 5: Pubblicazione sullo Store
Trappola: numeri di build riutilizzati
Sintomo: “The bundle version must be higher than the previously uploaded version” su iOS, oppure “Version code has already been used” su Google Play.
Soluzione: genera il numero in CI. Con fastlane, leggi l'ultimo build di TestFlight e aggiungi uno. Con Capgo Build, questo è il comportamento predefinito: recupera l'ultimo numero di build da App Store Connect o il più alto versionCode da Google Play e lo incrementa.
Problema: build bloccate in TestFlight
Sintomo: l'upload ha successo ma i tester non vedono mai la build.
Causa: risposta mancante sulla conformità all'export.
Soluzione: dichiaralo una volta in ios/App/App/Info.plist se usi solo crittografia standard:
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
Problema: rifiuti del privacy manifest
Sintomo: email da Apple relativa a dichiarazioni API obbligatorie mancanti (ITMS-91053).
Soluzione: aggiungi un PrivacyInfo.xcprivacy al target dell'app e aggiorna i plugin che ne includono uno proprio. Consulta il guida al privacy manifest per app iOS.
Trappola: tipo di artefatto errato
Soluzione: Google Play richiede un AAB (bundleRelease), non un APK. bundleRelease comunque riesce senza una release di firma e genera un AAB non firmato che Play rifiuta, quindi configura signingConfigs.release nella android/app/build.gradle prima. iOS richiede un export per l'App Store, non un IPA di sviluppo o ad hoc. Verifica il metodo di export nel tuo step di build.
Fase 6: Live updates
Trappola: pubblicare un live update che richiede una nuova build nativa
Sintomo: dopo un aggiornamento over-the-air, l'app crasha chiamando un metodo di un plugin che non esiste nel binario installato.
Causa: il web bundle dipende da una versione di un plugin più recente di quella compilata nell'app sui dispositivi degli utenti.
Soluzione: lascia che sia la pipeline a decidere. build needed esce con 0 quando le dipendenze native corrispondono a quelle live sul canale e con 1 quando serve un nuovo binario:
if bunx @capgo/cli@latest build needed com.example.app --channel production; then
bunx @capgo/cli@latest bundle upload com.example.app --channel production
else
bunx cap sync
bunx @capgo/cli@latest build request com.example.app --platform ios
bunx @capgo/cli@latest build request com.example.app --platform android
fi
Forza anche il percorso nativo quando i file sotto ios/, android/, o capacitor.config.* cambiano. Il pattern completo è in Scegli automaticamente live update o Native Build, e le regole di compatibilità sono in compatibilità nativa.
La soluzione che elimina il maggior numero di problemi
Se devi cambiare una sola cosa, sposta la compilazione e la firma iOS fuori dai tuoi CI runner. Configurazione Keychain, aggiornamenti Xcode, costi macOS e installazione profili spariscono dalla tua pipeline quando un job Linux passa il progetto preparato a Build Capgo:
bun install --frozen-lockfile && bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
Le criticità delle fasi 1, 5 e 6 restano valide, perché riguardano il progetto e non il runner. Per saperne di più sul debugging dei job falliti, vedi Risolvere gli errori di build nelle pipeline CI/CD di Capacitor.
Riferimento rapido
| Errore | Trappola | Sezione |
|---|---|---|
| UI vecchia in una nuova build | cap sync prima della web build |
Fase 1 |
No profiles for ... were found |
Profilo non installato o non corrispondente | Fase 3 |
errSecInternalComponent |
Keychain bloccata | Fase 3 |
MAC verification failed |
Password errata o OpenSSL 3 .p12 |
Fase 3 |
Unsupported class file major version |
JDK errato | Fase 2 |
| SDK troppo vecchio sull'upload | Xcode precedente alla versione 26 | Fase 2 |
| La versione del bundle deve essere superiore | Numero di build riutilizzato | Fase 5 |
| Versione code già utilizzata | Riutilizzato versionCode |
Fase 5 |
| Crash dopo live update | Modifica nativa distribuita over the air | Fase 6 |