Skip to content

Crashlytics NDK upload doesn't cleanup previous run's intermediate files when upload fails #8157

Description

@Sharkaboi

Describe your environment

  • Android Studio version: Narwhal | 2025.1
  • Firebase Component: Crashlytics NDK (gradle plugin + buildtools)
  • Component version: com.google.firebase:firebase-crashlytics-gradle:3.0.7 (also reproducible on 3.0.6), AGP 8.x, Gradle 8.13, JDK 17

Describe the problem

I have uploadCrashlyticsSymbolFile<Variant>Release set to run after bundle<Variant>Release (was necessary since the plugin does not auto-upload #7582).

While uploading the symbols, hit a server error :

> java.io.IOException: Unknown error while sending file, check network [/<repo path>/build/crashlytics/<flavor>Release/nativeSymbols/<upload artifact>.gz; response: 503 HTTP/1.1 503 Service Unavailable]

I retried and it failed again with error :

> java.io.FileNotFoundException: /<repo path>/build/crashlytics/<flavor>Release/nativeSymbols/<same upload artifact>.gz (No such file or directory)

I then ran rm -rf ./build/crashlytics which then completed successfully.

Steps to reproduce:

  1. Set finalizedBy uploadCrashlyticsSymbolFile<Variant>Release on bundle<Variant>Release:

    tasks.configureEach { task ->
        def m = task.name =~ /^bundle(.+)Release$/
        if (m) task.finalizedBy("uploadCrashlyticsSymbolFile" + m[0][1] + "Release")
    }
  2. Run ./gradlew bundle<Variant>Release which hits 503 from upload task.

  3. Without making any change, rerun the same command

Relevant Code:

  1. No try/finally in FirebaseSymbolFileService.uploadNativeSymbolFile
    (source).
    gZippedSymbolFile.delete() is sequential code after webApi.uploadFile() — when uploadFile
    throws on a 503, the .gz is never deleted and is left as an orphan in the output directory.

  2. No try/finally in Buildtools.uploadNativeSymbolFiles
    (source).
    symbolFile.delete() also does not run on exception, and the for-loop aborts on the first
    failure — every .sym that hadn't yet been processed is also stranded.

  3. No filter on dir.listFiles() in Buildtools.uploadNativeSymbolFiles
    (source): the next run iterates every entry
    in nativeSymbols/, including the orphan .gz. For an input named orphan.gz, the code computes
    gZippedSymbolFile = parent + removeExtension("orphan.gz") + ".gz" = same path as the input,
    and FileUtils.gZipFile(orphan.gz, orphan.gz) truncate-rewrites the file while a
    FileInputStream on it is still open.

  4. RestfulWebApi.uploadFile (source) makes a single attempt with no retry on 5xx, so a transient backend issue fails the build.

Workaround that I'm using to delete previous run's possible orphans :

tasks.matching { it.name.startsWith("uploadCrashlyticsSymbolFile") }.configureEach {
    doFirst {
        def dir = symbolFileDirectory.get().asFile
        if (dir.exists()) {
            dir.listFiles({ f -> !f.name.endsWith(".sym") } as FileFilter)
                ?.each { it.delete() }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions