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()

Open
#8,594 0 comments 0 reactions 0 assignees View on GitHub
api: sessions
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.