-
Notifications
You must be signed in to change notification settings - Fork 162
Description
Build/Submit details page URL
https://github.com/amehmeto/TiedSiren51/actions/runs/19090423640/job/54539925678
Summary
When running eas build --local inside a GitHub Actions workflow, environment variables defined in eas.json with the $VARIABLE_NAME syntax are not resolved from the GitHub Actions environment. Instead, the literal string "$VARIABLE_NAME" is passed to the build environment, causing build failures.
This creates a significant limitation for developers who want to use local builds in CI for testing (PRs) and cloud builds for production (merges).
Managed or bare?
Managed
Environment
expo-env-info 2.0.7 environment info:
System:
OS: macOS 14.6.1
Shell: 5.9 - /bin/zsh
Binaries:
Node: 18.18.2 - ~/Library/Caches/fnm_multishells/52698_1762110251287/bin/node
Yarn: 1.22.22 - ~/.yarn/bin/yarn
npm: 9.8.1 - ~/Library/Caches/fnm_multishells/52698_1762110251287/bin/npm
Watchman: 2025.02.17.00 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.15.2 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 24.2, iOS 18.2, macOS 15.2, tvOS 18.2, visionOS 2.2, watchOS 11.2
IDEs:
Android Studio: 2025.1 AI-251.27812.49.2514.14217341
Xcode: 16.2/16C5032a - /usr/bin/xcodebuild
npmPackages:
expo: ~51.0.39 => 51.0.39
expo-router: ~3.5.24 => 3.5.24
react: 18.2.0 => 18.2.0
react-dom: 18.2.0 => 18.2.0
react-native: 0.74.5 => 0.74.5
react-native-web: ~0.19.10 => 0.19.13
npmGlobalPackages:
eas-cli: 16.26.0
Expo Workflow: bare [I'm surprised it says that, I .gitignored android/ and ios/ dir, but I use custom expo modules]
env: load .env.local .env.development .env
env: export EXPO_PUBLIC_FIREBASE_API_KEY EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN EXPO_PUBLIC_FIREBASE_PROJECT_ID EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID EXPO_PUBLIC_FIREBASE_APP_ID EXPO_PUBLIC_FIREBASE_MEASUREMENT_ID EXPO_ROUTER_APP_ROOT
16/16 checks passed. No issues detected!
Error output
Current behavior
GitHub Actions workflow:
yaml- name: Build Android (Local)
env:
GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
run: eas build --platform android --local --profile production --non-interactive
eas.json:
json{
"build": {
"production": {
"env": {
"GOOGLE_SERVICES_JSON": "$GOOGLE_SERVICES_JSON"
}
}
}
}
Build logs:
To debug this issue, I added:
A eas-build-pre-install hook that logs what process.env.GOOGLE_SERVICES_JSON contains
A eas-build-post-install hook that uses cat to verify the generated file
[PRE_INSTALL_HOOK] Script 'eas-build-pre-install' is present in package.json, running it...
[PRE_INSTALL_HOOK] > node scripts/setup-google-services.cjs
[PRE_INSTALL_HOOK] === setup-google-services.js ===
[PRE_INSTALL_HOOK] Current directory: /tmp/runner/eas-build-local-nodejs/6541f039-b4a7-482b-80f4-5b89957ab2ee/build
[PRE_INSTALL_HOOK] Environment variables available: [
[PRE_INSTALL_HOOK] 'EAS_BUILD_GIT_COMMIT_HASH',
[PRE_INSTALL_HOOK] 'GOOGLE_SERVICES_JSON',
[PRE_INSTALL_HOOK] ...
[PRE_INSTALL_HOOK] ]
[PRE_INSTALL_HOOK] EXPO_ROUTER_APP_ROOT resolve to: $EXPO_ROUTER_APP_ROOT
[PRE_INSTALL_HOOK] ❌ GOOGLE_SERVICES_JSON is not resolved (still contains $)
[PRE_INSTALL_HOOK] Value: $GOOGLE_SERVICES_JSON
[PRE_INSTALL_HOOK] 🔨 Decoding GOOGLE_SERVICES_JSON...
[PRE_INSTALL_HOOK] Base64 length: 21
[PRE_INSTALL_HOOK] Base64 preview: $GOOGLE_SERVICES_JSON...
[PRE_INSTALL_HOOK] 📝 Writing to: /tmp/runner/eas-build-local-nodejs/6541f039-b4a7-482b-80f4-5b89957ab2ee/build/google-services.json
[PRE_INSTALL_HOOK] ✅ google-services.json created successfully
[PRE_INSTALL_HOOK] File size: 22 bytes
[PRE_INSTALL_HOOK] File content preview:
[PRE_INSTALL_HOOK] �,O�HD�%#�
[POST_INSTALL_HOOK] Script 'eas-build-post-install' is present in package.json, running it...
[POST_INSTALL_HOOK] > cat google-services.json
[POST_INSTALL_HOOK] �,O�HD�%#�
Analysis:
The variable GOOGLE_SERVICES_JSON exists in the environment (it's listed)
But its value is the literal string "$GOOGLE_SERVICES_JSON" (21 characters)
When the script tries to decode this as base64, it produces corrupted output (22 bytes of garbage)
The post-install hook confirms the file contains corrupted data
Interesting observation: EXPO_ROUTER_APP_ROOT also shows the same behavior (resolves to $EXPO_ROUTER_APP_ROOT), but this didn't break the build in previous attempts. This raises the question: at which stage are environment variables actually resolved, and why does GOOGLE_SERVICES_JSON behave differently or cause a failure when other unresolved variables don't?
Expected behavior
When eas build --local runs in a CI environment where environment variables are set (e.g., GOOGLE_SERVICES_JSON from GitHub Secrets), the $GOOGLE_SERVICES_JSON reference in eas.json should be resolved from the shell environment, not treated as a literal string.
Question for the Expo team: What is the recommended way to pass environment variables from a CI provider (GitHub Actions) to eas build --local? The current $VARIABLE syntax in eas.json doesn't work for local builds, even when the variable is set in the CI environment.
Why this matters
Many developers want to use different build strategies based on the development stage:
My use case (and likely many others):
Open PRs: Use eas build --local in GitHub Actions for fast iteration and testing
PRs are expected to fail often during development
No point wasting EAS Cloud build credits on builds that will likely fail
Faster feedback loop for developers
Merged PRs: Use eas build (cloud) for production builds
These builds are expected to succeed
Take advantage of EAS infrastructure for distribution
Worth using build credits for stable, release-ready code
Reproducible demo or steps to reproduce from a blank project
Create a GitHub Actions workflow with a secret
Set up eas.json with "VARIABLE": "$VARIABLE" in the env section
Run eas build --local with the secret set as environment variable
Observe that the literal string "$VARIABLE" is passed instead of the value