EAS Build from CI gives "CombinedError: [Network] Unauthorized"

Hello everyone,

EAS Build works great on our local-dev and staging profiles, but fails with an obscure error when using production profile from CI.

I post here hoping someone will have an idea which would resolve this error, or a way to investigate this issue. Please ask me any other information which might be helpful for your understanding.

[ gitlab-ci.yml ]

variables:
  EXPO_TOKEN: $EXPO_TOKEN

default:
  image: node:16.13.1

stages:
  - setup
  - test
  - build
  - update
  - submission

setup:
  stage: setup
  only:
    - dev
    - /^(ci|feat|bug|fix|chore|task)/
    - production
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
  script:
    - yarn set version classic
    - yarn

test:
  stage: test
  only:
    - dev
    - /^(ci|feat|bug|fix|chore|task)/
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
    policy: pull
  script:
    - yarn set version classic
    - yarn lint
    - yarn test

build-preprod:
  stage: build
  only: [dev]
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
    policy: pull
  script:
    - yarn build:staging:ci
  after_script:
    - echo "=== Staging Build complete ==="

update-preprod:
  stage: update
  only: [dev]
  when: manual
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
    policy: pull
  script:
    - yarn update:staging:ci
    - ls -al ./dist
  after_script:
    - echo "=== Staging Update complete ==="

build-prod:
  stage: build
  only: [production]
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
    policy: pull
  script:
    - yarn build:production:ci
  after_script:
    - echo "=== Production Build complete ==="

update-prod:
  stage: update
  only: [production]
  when: manual
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
    policy: pull
  script:
    - yarn update:production:ci
  after_script:
    - echo "=== Production Update complete ==="

submit-to-stores:
  stage: submission
  only: [production]
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules
    policy: pull
  script:
    - yarn submit:production:ci
  after_script:
    - echo "=== Production Update complete ==="

[ Sections of package.json - non-relevant content has been removed. ]

{
  "scripts": {
    "start": "expo start --clear",
    "android": "expo start --android",
    "build:dev": "eas build --profile dev",
    "build:dev:ios-simulator": "eas build -p ios --profile dev-ios-simulator",
    "build:staging": "eas build --profile staging --platform all --no-wait",
    "build:staging:ci": "eas build --profile staging --platform all --non-interactive --no-wait",
    "build:production": "eas build --profile production --platform all --no-wait",
    "build:production:ci": "eas build --profile production --platform all --non-interactive --no-wait --verbose",
    "update:staging": "eas update --branch dev",
    "update:staging:ci": "eas update --branch dev --non-interactive --auto",
    "update:production": "eas update --branch production",
    "update:production:ci": "eas update --branch production --non-interactive --auto",
    "submit:production:ci": "eas submit --platform all --non-interactive --no-wait",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "eject": "expo eject"
  },
  "dependencies": {
    "expo": "^45.0.0",
    "expo-ads-admob": "~13.0.0",
    "expo-app-loading": "~2.0.0",
    "expo-application": "~4.1.0",
    "expo-barcode-scanner": "~11.3.0",
    "expo-chart-kit": "^1.2.4",
    "expo-constants": "~13.1.1",
    "expo-dev-client": "~1.0.1",
    "expo-device": "~4.2.0",
    "expo-font": "~10.1.0",
    "expo-haptics": "~11.2.0",
    "expo-intent-launcher": "~10.2.0",
    "expo-linear-gradient": "~11.3.0",
    "expo-localization": "~13.0.0",
    "expo-location": "~14.2.2",
    "expo-notifications": "~0.15.4",
    "expo-screen-orientation": "~4.2.0",
    "expo-splash-screen": "~0.15.1",
    "expo-status-bar": "~1.3.0",
    "expo-system-ui": "~1.2.0",
    "expo-tracking-transparency": "~2.2.0",
    "expo-updates": "~0.13.4"
  },
  "devDependencies": {
    "eas-cli": "^2.3.0",
    "expo-cli": "^6.0.6",
  },
  "private": true,
  "engines": {
    "node": ">=16.13.1",
    "yarn": ">=1.22.15"
  }
}

[ eas.json ]

{
  "cli": {
    "version": ">= 0.52.0"
  },
  "build": {
    "dev": {
      "developmentClient": true,
      "distribution": "internal",
      "env": {
        "API_ENDPOINT": "<secret-removed>",
        "MIXPANEL_TOKEN": "<secret-removed>"
      }
    },
    "dev-ios-simulator": {
      "distribution": "internal",
      "env": {
        "API_ENDPOINT": "<secret-removed>",
        "MIXPANEL_TOKEN": "<secret-removed>"
      },
      "ios": {
        "simulator": true
      }
    },
    "staging": {
      "channel": "staging",
      "distribution": "internal"
    },
    "production": {
      "channel": "production"
    }
  },
  "submit": {
    "production": {}
  }
}

[ app.config.js ]

// See doc at --> https://docs.expo.io/versions/latest/config/app/

export default {
  expo: {
    name: '<secret-removed>',
    // References <secret-removed> Expo's Organization
    owner: '<secret-removed>',
    description: `<secret-removed>`,
    slug: '<secret-removed>',
    version: '2.1.0',
    platforms: ['ios', 'android'],
    orientation: 'portrait',
    userInterfaceStyle: 'automatic',
    icon: './assets/images/appicon.png',
    scheme: '<secret-removed>',
    runtimeVersion: {
      policy: 'sdkVersion',
    },
    extra: {
      eas: {
        projectId: 'c86bb2a8-ed60-4144-a5f4-6ecbdd7e5450', // Kept this for you Expo Team.
      },
    },
    updates: {
      // Some users might need to update the app using very slow network.
      fallbackToCacheTimeout: 300000,
      url: 'https://u.expo.dev/<secret-removed>',
    },
    assetBundlePatterns: ['**/*'],
    splash: {
      image: './assets/images/splash.png',
      resizeMode: 'contain',
      backgroundColor: '#ffffff',
    },
    ios: {
      buildNumber: '2.1.0',
      bundleIdentifier: 'com.<secret-removed>.<secret-removed>',
      infoPlist: {
        // CFBundleAllowMixedLocalizations: Permits the use of "locales" iOS translations.
        // Translation messages like "NS..." should be set for each locale in locale files.
        // https://docs.expo.dev/distribution/app-stores/?redirected#localizing-your-ios-app
        CFBundleAllowMixedLocalizations: true,
        UIUserInterfaceStyle: 'automatic',
      },
      userInterfaceStyle: 'automatic',
      config: {
        googleMapsApiKey: '<secret-removed>',
        // googleMobileAdsAppId - https://docs.expo.dev/versions/latest/sdk/admob/
        // We don't make use of Ads Mob, but Expo SDK references it so it is required to have an App ID even if not using the feature.
        // Might be removed on SDK 46.
        // https://apps.admob.com/v2/apps/list?utm_source=internal&utm_medium=et&utm_campaign=helpcentrecontextualopt&utm_term=http%3A%2F%2Fgoo.gl%2F6Xkfcf&subid=ww-ww-et-amhelpv4
        googleMobileAdsAppId: 'ca-app-pub-<secret-removed>',
      },
    },
    locales: {
      fr: './ios-permissions-locales/fr.json',
      en: './ios-permissions-locales/en.json',
      es: './ios-permissions-locales/es.json',
      pt: './ios-permissions-locales/pt.json',
    },
    android: {
      versionCode: 210,
      softwareKeyboardLayoutMode: 'pan',
      package: 'com.<secret-removed>.<secret-removed>',
      config: {
        googleMaps: {
          apiKey: '<secret-removed>',
        },
        // googleMobileAdsAppId - https://docs.expo.dev/versions/latest/sdk/admob/
        // We don't make use of Ads Mob, but Expo SDK references it so it is required to have an App ID even if not using the feature.
        // Might be removed on SDK 46.
        // https://apps.admob.com/v2/apps/list?utm_source=internal&utm_medium=et&utm_campaign=helpcentrecontextualopt&utm_term=http%3A%2F%2Fgoo.gl%2F6Xkfcf&subid=ww-ww-et-amhelpv4
        googleMobileAdsAppId: 'ca-app-pub-<secret-removed>',
      },
      // https://docs.expo.dev/guides/permissions/
      // Note: We don't add any permission here because this would override EVERY OTHER permission.
      // Permissions are injected automatically by Expo's modules at compilation time.
      // If you need to add something here, you MUST know that this will restrict the app to using
      // ONLY the permissions you mention here. Review Expo's reco first.
      // vibrate: ['VIBRATE'],
      // Expo push notifications
      // https://console.firebase.google.com/project/expo-push-notifications-2354d/settings/cloudmessaging/android:com.<secret-removed>.<secret-removed>
      // https://console.cloud.google.com/apis/credentials?project=<secret-removed>
      googleServicesFile: './google-services.json',
    },
    plugins: ['sentry-expo', 'expo-tracking-transparency'],
    hooks: {
      postPublish: [
        {
          file: 'sentry-expo/upload-sourcemaps',
          config: {
            organization: '<secret-removed>',
            project: '<secret-removed>',
            authToken: '<secret-removed>',
          },
        },
      ],
    },
  },
}

[ The CI “build-prod” log ]

Running with gitlab-runner 15.6.0~beta.186.ga889181a (a889181a)
  on blue-1.shared.runners-manager.gitlab.com/default <secret-removed>
Preparing the "docker+machine" executor
00:31
Using Docker executor with image node:16.13.1 ...
Pulling docker image node:16.13.1 ...
Using docker image sha256:8381a2328ac23db134520977fdc4c4c34ba6afbf1b7bc72c62ad086db86003a2 for node:16.13.1 with digest node@sha256:32605ead97ed57bd39a8a7b0e919240e1a3218974dfc6965e61b54a801753131 ...
Preparing environment
00:04
Running on runner-j<secret-removed>-concurrent-0 via runner-<secret-removed>...
Getting source from Git repository
00:03
$ eval "$CI_PRE_CLONE_SCRIPT"
Fetching changes with git depth set to 50...
Initialized empty Git repository in /builds/<secret-removed>/expo-app/.git/
Created fresh repository.
Checking out f80ab9bc as production...
Skipping Git submodules setup
Restoring cache
00:29
Checking cache for <secret-removed>-1-non_protected...
Downloading cache.zip from https://storage.googleapis.com/gitlab-com-runners-cache/project/<secret-removed>/<secret-removed>-1-non_protected 
Successfully extracted cache
Executing "step_script" stage of the job script
00:05
Using docker image sha256:8381a2328ac23db134520977fdc4c4c34ba6afbf1b7bc72c62ad086db86003a2 for node:16.13.1 with digest node@sha256:32605ead97ed57bd39a8a7b0e919240e1a3218974dfc6965e61b54a801753131 ...
$ yarn build:production:ci
yarn run v1.22.15
$ eas build --profile production --platform all --non-interactive --no-wait --verbose
    CombinedError: [Network] Unauthorized
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Running after_script
00:01
Running after script...
$ echo "=== Production Build complete ==="
=== Production Build complete ===
Cleaning up project directory and file based variables
00:01
ERROR: Job failed: exit code 1


Production profile doesn’t work outside of CI either FYI.

We upgraded eas-cli to 2.6.0 and now it works locally but still doesn’t work on CI.

I found an issue in our GitLab variables configuration which cause EXPO_TOKEN to be missing.

I wished the message be explicit if EXPO_TOKEN is missing…

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.