firebase / firebase/firebase-android-sdk
firebase-sessions: IOException "Failed to create directory: …/files/datastore/firebaseSessions" is rethrown from prepDataStoreFile and kills the process before Application.onCreate()
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 710
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 31
Description
### [READ] Step 1: Are you in the right place?
Yes — this is a runtime crash inside `firebase-sessions`, not a Firebase console / backend question.
### Environment
- **Firebase Component:** Sessions (transitive dependency of Crashlytics)
- **Component version:** `firebase-sessions` 3.0.7 (BoM 34.18.0, crash observed in production) — bytecode of the faulty method checked identical in 3.0.8 (BoM 34.19.0), see "Analysis"
- **Kotlin / coroutines:** 2.3.21 / 1.10.x
- **Device:** Xiaomi / MIUI (frames `miui.process.*`, `miui.contentcatcher.*` on the main thread), Android
- **Reproduction:** the device-side condition is not reproducible on a healthy device or emulator; an equivalent failure is reproducible on an emulator (see below)
### [REQUIRED] Step 3: Describe the problem
**Fatal crash on app launch, before `Application.onCreate()` runs**, when `firebase-sessions` cannot create its DataStore directory. The SDK rethrows the `IOException` from `prepDataStoreFile()` inside a `launch { }` on its own scope with no `CoroutineExceptionHandler`, so kotlinx.coroutines forwards it to `Thread.defaultUncaughtExceptionHandler` and the process dies. The app is unusable on that device until the underlying mount is fixed — a condition the app has no control over.
Stack trace (Crashlytics, app version 6.2.9, 2026-09-05). The exception is raised on `Firebase Background Thread #0`, inside a DataStore read started by the Sessions component:
```
Fatal Exception: java.io.IOException: Failed to create directory: /data/user/0/fr.tisseo.android/files/datastore/firebaseSessions
at com.google.firebase.sessions.FirebaseSessionsComponent$MainModule$Companion.prepDataStoreFile(FirebaseSessionsComponent.kt:237)
at com.google.firebase.sessions.FirebaseSessionsComponent$MainModule$Companion.sessionConfigsDataStore$lambda$2(FirebaseSessionsComponent.kt:154)
at androidx.datastore.core.FileStorage.createConnection(FileStorage.kt:48)
at androidx.datastore.core.DataStoreImpl.storageConnectionDelegate$lambda$0(DataStoreImpl.kt:196)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:86)
at androidx.datastore.core.DataStoreImpl.getStorageConnection$datastore_core(DataStoreImpl.kt:197)
at androidx.datastore.core.DataStoreImpl.coordinator_delegate$lambda$0(DataStoreImpl.kt:198)
at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:86)
at androidx.datastore.core.DataStoreImpl.getCoordinator(DataStoreImpl.kt:198)
at androidx.datastore.core.DataStoreImpl.readAndInitOrPropagateAndThrowFailure(DataStoreImpl.kt:284)
at androidx.datastore.core.DataStoreImpl.access$readAndInitOrPropagateAndThrowFailure(DataStoreImpl.kt:46)
at androidx.datastore.core.DataStoreImpl$readState$2.invokeSuspend(DataStoreImpl.kt:232)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1100)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at com.google.firebase.concurrent.CustomThreadFactory.lambda$newThread$0(CustomThreadFactory.java:47)
at java.lang.Thread.run(Thread.java:1572)
Caused by java.nio.file.FileSystemException: /data/user/0/fr.tisseo.android/files/datastore/firebaseSessions: Operation not supported on transport endpoint
at sun.nio.fs.UnixFileSystemProvider.createDirectory(UnixFileSystemProvider.java:404)
at java.nio.file.Files.createDirectory(Files.java:690)
at java.nio.file.Files.createAndCheckIsDirectory(Files.java:797)
at java.nio.file.Files.createDirectories(Files.java:783)
at com.google.firebase.sessions.FirebaseSessionsComponent$MainModule$Companion.prepDataStoreFile(FirebaseSessionsComponent.kt:235)
at com.google.firebase.sessions.FirebaseSessionsComponent$MainModule$Companion.sessionConfigsDataStore$lambda$2(FirebaseSessionsComponent.kt:154)
... (same DataStore frames as above)
```
How it reaches the process: the `StandaloneCoroutine` completes with the exception and, with no `CoroutineExceptionHandler` in its context, kotlinx.coroutines hands it to the default uncaught exception handler — which is Crashlytics', installed by `FirebaseInitProvider`:
```
Firebase Background Thread #0:
at com.google.firebase.crashlytics.internal.common.CrashlyticsController.handleUncaughtException(CrashlyticsController.java:182)
at com.google.firebase.crashlytics.internal.common.CrashlyticsController$1.onUncaughtException(CrashlyticsController.java:169)
at com.google.firebase.crashlytics.internal.common.CrashlyticsUncaughtExceptionHandler.uncaughtException(CrashlyticsUncaughtExceptionHandler.java:54)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1098)
at kotlinx.coroutines.internal.CoroutineExceptionHandlerImplKt.propagateExceptionFinalResort(CoroutineExceptionHandlerImpl.kt:33)
at kotlinx.coroutines.internal.CoroutineExceptionHandlerImpl_commonKt.handleUncaughtCoroutineException(CoroutineExceptionHandlerImpl.common.kt:48)
at kotlinx.coroutines.CoroutineExceptionHandlerKt.handleCoroutineException(CoroutineExceptionHandler.kt:31)
at kotlinx.coroutines.StandaloneCoroutine.handleJobException(Builders.common.kt:524)
at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:222)
...
```
At that moment the main thread is still inside `super.onCreate()` of the `Application` (a MIUI hook, `miui.contentcatcher.InterceptorProxy.addMiuiApplication`), i.e. the app has not executed a single line of its own `onCreate()` yet:
```
main:
at miui.process.ProcessManagerProxy.addMiuiApplicationThread(ProcessManagerNative.java:530)
at miui.contentcatcher.InterceptorProxy.addMiuiApplication(InterceptorProxy.java:84)
at android.app.ApplicationImpl.onCreate(ApplicationImpl.java:19)
at android.app.Application.onCreate(Application.java:251)
at fr.tisseo.android.application.Hilt_MainApplication.onCreate(Hilt_MainApplication.java:44)
at fr.tisseo.android.application.MainApplication.onCreate(MainApplication.kt:69)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1212)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6977)
```
`ENOTCONN` ("Operation not supported on transport endpoint") on a `/data/user/0/…` path points at a non-standard mount of the app's private storage — typically a cloned app ("Dual apps" / "Second space" on MIUI), a virtualization container, or a broken FUSE mount. The app itself does nothing with that directory.
#### Steps to reproduce
The MIUI mount state cannot be reproduced at will. The same failure is reproducible on any emulator by making the directory creation fail:
1. Install an app using `firebase-crashlytics` (hence `firebase-sessions`) and launch it once.
2. Replace the DataStore directory with a dangling symlink:
```
adb shell run-as sh -c 'rm -rf files/datastore/firebaseSessions && ln -s /nonexistent files/datastore/firebaseSessions'
```
3. Force-stop and relaunch the app.
Result: `IOException: Failed to create directory: …/files/datastore/firebaseSessions` raised from `prepDataStoreFile` on `Firebase Background Thread #n`, Crashlytics logs `Handling uncaught exception "java.io.IOException: Failed to create directory: …"`, the process is killed before `Application.onCreate()` reaches its first line.
#### Analysis
- `prepDataStoreFile()` (introduced by #7440) handles one failure mode — an unexpected *file* where the directory should be, which it deletes — then calls `Files.createDirectories()` and wraps any other failure into an `IOException` that it **rethrows**. The improved error message is welcome, but the behaviour is still "crash the host app".
- The `IOException` propagates out of the SDK's `launch { }` (no `CoroutineExceptionHandler` on the scope), so it reaches the default uncaught exception handler.
- In 3.0.8 the `sessionDataStore` path already degrades ("Emit fallback session") but `sessionConfigsDataStore` still rethrows (`FirebaseSessionsComponent.kt:154` in 3.0.8) — the crash is unchanged between 3.0.7 and 3.0.8.
- Because the component is initialized by `FirebaseInitProvider`, i.e. **before `Application.onCreate()`**, the host app has no hook to catch it: the Crashlytics uncaught-exception handler is already in front of anything installed from `onCreate()`. The only workaround we found is a `Thread.UncaughtExceptionHandler` installed from `Application.attachBaseContext()` that absorbs this exact signature — and even then Crashlytics still files it as a fatal, since it sees the exception first.
- `firebase_sessions_enabled=false` in the manifest does not help: the DataStore is created by the Dagger component before `LocalOverrideSettings` is consulted.
#### Expected behaviour
Sessions telemetry is optional for the host app. A failure to create or open its DataStore should **degrade the Sessions SDK** (disable sessions for this run, log a warning, and let Crashlytics fall back to a session without Sessions data — as the fallback-session path already does) rather than kill the process before the app even starts.
Concretely:
- catch `IOException` around `prepDataStoreFile()` / the DataStore construction and fall back to an in-memory / no-op store, or
- run the DataStore initialization in a scope with a `CoroutineExceptionHandler`, so an unexpected failure is logged instead of propagated to the default uncaught exception handler.
#### Relevant Code
```kotlin
// FirebaseSessionsComponent.kt (3.0.7 / 3.0.8), MainModule.Companion
private fun prepDataStoreFile(context: Context, fileName: String): File {
val file = context.filesDir.resolve("datastore/$fileName")
// ...unexpected file at the directory path is removed...
try {
Files.createDirectories(file.parentFile!!.toPath())
} catch (ex: Exception) {
throw IOException("Failed to create directory: ${file.parentFile}", ex) // rethrown into a launch { } with no handler → process death
}
return file
}
```
Related: #7440 (introduced the current code), #3452 (same "cannot create directory under files/" pattern in Crashlytics, also on Xiaomi, closed by bot without a fix), #7518 (ANR in `createDataStore` during the same early initialization).
Contributor guide
Research direction
Start in FirebaseSessionsComponent.kt at prepDataStoreFile and sessionConfigsDataStore, then reproduce the failure with the provided run-as symlink and relaunch steps. Trace how the DataStore exception leaves the launch scope and compare it with the existing fallback-session path. Done means a directory or DataStore failure is logged or degraded without killing the process before Application.onCreate().
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100