◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 34 days left. If your app calls it, check the migration target●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 34 days left. If your app calls it, check the migration target●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-10-27Intermediate

You Changed the Environment Variable and Rebuilt for Android, but the App Still Points at the Old Server. Look at the Line That Says UP-TO-DATE

Change only an EXPO_PUBLIC_ value and rebuild a release for Android, and the bundle task can be skipped as UP-TO-DATE, leaving the old value inside. I reproduce the same behavior in a minimal Gradle project and cover how to spot it and what to do until it is fixed.

Expo229Android63Gradle4Environment Variables4EXPO_PUBLIC4Troubleshooting51SDK 573

I might have shipped a production build that still talks to the development API. The first hint would not be the app's behavior. It would be one line in the build log.

> Task :app:createBundleReleaseJsAndAssets UP-TO-DATE

Say you change EXPO_PUBLIC_API_URL to the production value and rebuild the Android release. The build ends with "BUILD SUCCESSFUL". Yet the app you get may still carry last time's development value. A report on Expo's GitHub describes exactly this.

Let me put the conclusion first. To Gradle, a build where only a value changed looks like a build where nothing changed, and it can skip regenerating the JavaScript bundle. Below I keep what the report confirms separate from what I reproduced myself.

How to recognize it

The conditions in the report are quite specific.

What to checkIf the answer is yes
Did you change only an environment variable or a .env value (no JavaScript source touched)?You match. With identical sources, Gradle decides the inputs are unchanged
Does the build log show `createBundleReleaseJsAndAssets UP-TO-DATE`?Then the bundle was not regenerated
Are you building repeatedly in the same `android` folder?It happens when the previous output is still there. A fresh checkout should be less exposed, but the report does not confirm that
Does the variable start with `EXPO_PUBLIC_`?Those values are inlined into the bundle at build time, not read when the app runs

The report is Issue 49630 in expo/expo. It was opened on September 1, 2026 and observed on SDK 57, React Native 0.86, macOS, and a bare Android project. Expo's automated triage on the same issue says it reproduced the problem, and adds that a changed .env file behaves the same as a changed shell variable.

Why it gets skipped

Values starting with EXPO_PUBLIC_ are written into the bundle as plain strings while it is built, not at runtime. So if the value changes, the bundle's contents should change too.

But Gradle only looks at what a task has declared as inputs. For the bundle task, those are the JavaScript sources and the bundle settings. The environment variables the bundler reads are not on the list. Inputs and outputs look the same as last time, so Gradle concludes there is nothing to do and prints UP-TO-DATE.

In short, the bundler depends on the environment while Gradle does not know it. That mismatch is the whole cause.

Reproducing it in a minimal project

Reading a report gives me little to hold on to. So I recreated the same mismatch with Gradle alone, no Android and no Expo. The task imitates the bundle task: its only input is a src folder, its output is one file, and the contents come from an environment variable.

tasks.register('createBundle') {
  inputs.dir('src')                                   // input: sources only
  outputs.file(layout.buildDirectory.file('index.bundle'))
  doLast {
    def out = layout.buildDirectory.file('index.bundle').get().asFile
    out.parentFile.mkdirs()
    // stands in for the bundler inlining an environment variable
    out.text = "var API = '" + (System.getenv('EXPO_PUBLIC_API_URL') ?: '') + "';\n"
  }
}

Running it twice with a different value gave this (Gradle 8.14.3).

# Run 1: EXPO_PUBLIC_API_URL=https://dev.example.invalid
> Task :createBundle
var API = 'https://dev.example.invalid';

# Run 2: EXPO_PUBLIC_API_URL=https://prod.example.invalid
> Task :createBundle UP-TO-DATE
var API = 'https://dev.example.invalid';

Run 2 was UP-TO-DATE and the output kept the development value. The build counts as a success, so skimming the log, you would not notice.

The fix: declare the variables as inputs

The workaround in the report is to tell Gradle about the EXPO_PUBLIC_ variables as inputs of the bundle task. I added the same idea to my minimal project with two lines.

def pub = providers.environmentVariablesPrefixedBy('EXPO_PUBLIC_')
tasks.named('createBundle') { inputs.property('expoPublicEnv', pub) }

With that in place, I ran three builds in order.

# Run 1: dev value
> Task :createBundle                 → https://dev.example.invalid
# Run 2: prod value
> Task :createBundle                 → https://prod.example.invalid
# Run 3: prod value again
> Task :createBundle UP-TO-DATE

The run with a changed value executed again, and only the unchanged third run was skipped. You keep the speed and lose the mix-up.

Here is what I did not verify. That behavior comes from my own minimal model. In a real Expo project you would use the form in the report, which adds the input to tasks whose names start with createBundle and end with JsAndAssets. I did not try it on a real Android project or device.

Until it is fixed

Expo has a change that declares these inputs automatically, opened as Pull Request 49839. As of October 27, when I checked, it had not been merged. Until then, pick one of these.

  • For builds where only a value changed, add --rerun-tasks to force every task to run again. It is reliable, but the whole build gets slower.
  • Add the declaration above to your Android build settings. If you regenerate the android folder each time, what you added will be wiped.
  • Make production builds in an environment with no previous output, such as a clean checkout.

Whether EAS cloud builds are affected is listed as unconfirmed in the report. I have not checked either, so I won't claim anything.

One small step before you ship

As an indie developer who keeps Android apps in the store, I would rather catch a wrong server address in my own log than in a user report. In the end, the most dependable habit is to look inside what you built. A successful build and the right value being inside are different facts. Once the release file exists, search it once for the development address before you publish.

A success message is not a guarantee about the contents. On days when I catch myself forgetting that, I go back to the log and look for UP-TO-DATE.

Stale state surviving a deploy has a cousin on the web side, which I dig into from the cause in Why Redeploying Doesn't Fix the Application Error You See Right After a Deploy.

The next time you switch the server your build points at, open the build log first and check only whether the createBundle line says UP-TO-DATE.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◇ App Dev2026-10-15
When Only EAS Build Fails with "No matching variant", Check Your Local android/build First
Gradle passes on your machine, but the Android build on EAS stops at "No matching variant of project". Reading the reports on GitHub, I traced how a Windows-generated autolinking cache can reach the Linux builder, and wrote a small script that checks for it before you queue a build.
◇ App Dev2026-10-21
expo-widgets Works on iOS but Fails Only in the Android Build — Pin the Version or Exclude It?
Reports say expo-widgets 58.0.17 and later stops at Kotlin compilation on Android only. How to recognize it, when to pin versus exclude from autolinking, and a script that checks your project.
◇ App Dev2026-10-18
It Ran Fine in Debug, but the Release Build Died on Launch with a Resource Not Found. A File I Looked Up by Name Had Been Shrunk Away
A release-only crash at launch, caused by images and sounds looked up by a name string being removed by resource shrinking. A keep.xml written at prebuild, plus a post-build check that confirms it worked.