microg/GmsCore · error · IOException

Failed to create directories: ${parentDir.absolutePath}

Error message

Failed to create directories: ${parentDir.absolutePath}

What it means

HttpClient.download throws this IOException when the target file's parent directory does not exist and mkdirs() fails to create it. The library refuses to continue the download because writing the file would fail anyway, and includes the offending directory path in the message.

Source

Thrown at vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt:58

    private val client = singleInstanceOf { HttpClient(OkHttp) {
        expectSuccess = true
        install(HttpTimeout)
    } }

    private val clientWithCache = singleInstanceOf { HttpClient(OkHttp) {
        expectSuccess = true
        install(HttpCache)
        install(HttpTimeout)
    } }

    suspend fun download(
        url: String,
        downloadFile: File,
        params: Map<String, String> = emptyMap()
    ): File = downloadFile.also { toFile ->
        val parentDir = downloadFile.getParentFile()
        if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
            throw IOException("Failed to create directories: ${parentDir.absolutePath}")
        }

        FileOutputStream(toFile).use { download(url, it, params) }
    }

    suspend fun download(
            url: String,
            downloadTo: OutputStream,
            params: Map<String, String> = emptyMap(),
            downloadedBytes: Long = 0,
            emitProgress: (bytesDownloaded: Long) -> Unit = {}
    ) {
        try {
            Log.d(TAG, "download downloadedBytes:$downloadedBytes")
            client.prepareGet(url.asUrl(params)){
                if (downloadedBytes > 0) {
                    headers {
                        append(HttpHeaders.Range, "bytes=$downloadedBytes-")

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Request MANAGE_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE or use app-scoped storage (getExternalFilesDir) instead
  2. Create the parent directory yourself beforehand with parentDir.mkdirs() and check the result
  3. Verify the path exists, is writable, and the filesystem is not read-only or full
  4. Download to internal cache dir first and copy afterwards if the destination is restricted

Example fix

// before
httpClient.download(url, File("/sdcard/downloads/file.bin"))
// after
val dest = File(context.getExternalFilesDir(null), "file.bin")
dest.parentFile?.mkdirs()
httpClient.download(url, dest)
Defensive patterns

Strategy: validation

Validate before calling

val parent = destFile.parentFile
if (parent != null && !parent.exists() && !parent.mkdirs()) {
    throw IOException("Cannot create ${parent}")
}

Type guard

fun File.isWritableTarget(): Boolean {
    val p = parentFile ?: return false
    return (p.exists() || p.mkdirs()) && p.canWrite()
}

Try / catch

try {
    httpClient.download(url, destFile)
} catch (e: IOException) {
    if (e.message?.startsWith("Failed to create directories") == true) {
        // fall back to app-scoped storage
    }
}

Prevention

When it happens

Trigger: Calling download(url, File("/some/path/file.bin")) where /some/path doesn't exist and cannot be created — e.g. missing WRITE_EXTERNAL/storage permission, read-only filesystem, path on unmounted storage, or invalid path characters.

Common situations: Downloading to external storage without runtime storage permission on Android 6+; using a path on scoped-storage-restricted locations (Android 10+); SD card removed or emulated storage full.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/155e813a48c8dd88. Report an issue: GitHub.