{"record":{"id":"193381f38554be20","repo":"immich-app/immich","slug":"cannot-open-original-stream-for-asset-assetid","errorCode":null,"errorMessage":"Cannot open original stream for asset $assetId","messagePattern":"Cannot open original stream for asset \\$assetId","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt","lineNumber":484,"sourceCode":"      HashResult(assetId, \"Failed to hash asset: ${e.message}\", null)\n    }\n  }\n\n  private fun openOriginalStream(assetId: String): InputStream {\n    val id = assetId.toLong()\n    val collection = when (getMediaType(id)) {\n      MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI\n      else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI\n    }\n    val uri = ContentUris.withAppendedId(collection, id)\n    val original = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n      MediaStore.setRequireOriginal(uri)\n    } else {\n      uri\n    }\n\n    return ctx.contentResolver.openInputStream(original)\n      ?: throw IOException(\"Cannot open original stream for asset $assetId\")\n  }\n\n  private fun getMediaType(id: Long): Int =\n    getCursor(\n      MediaStore.VOLUME_EXTERNAL,\n      \"${MediaStore.MediaColumns._ID} = ?\",\n      arrayOf(id.toString()),\n      arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE)\n    )?.use { cursor ->\n      if (cursor.moveToFirst()) {\n        cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE))\n      } else {\n        MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE\n      }\n    } ?: MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE\n\n  fun cancelHashing() {\n    hashTask?.cancel()","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt#L466-L502","documentation":"When hashing an asset, the resolver resolves the asset's MediaStore URI (optionally requiring the original file via MediaStore.setRequireOriginal) and opens an InputStream. If ContentResolver.openInputStream returns null — meaning the content provider could not open the file — this IOException is thrown with the asset id.","triggerScenarios":"openOriginalStream is called during asset hashing and ContentResolver.openInputStream(uri) returns null: the asset row exists in MediaStore but the backing file is missing, or setRequireOriginal original file is inaccessible.","commonSituations":"Cloud-synced photos whose local bytes were evicted (only placeholder available), assets deleted between query and hash, corrupted MediaStore entries, missing READ_MEDIA_IMAGES/ACCESS_MEDIA_LOCATION permissions causing provider denial.","solutions":["Verify storage/photo permissions (READ_MEDIA_IMAGES, ACCESS_MEDIA_LOCATION) are granted","Re-check the asset still exists in MediaStore before hashing and skip/refresh missing entries","Exclude or re-download cloud-only assets before hashing","Catch the IOException per-asset so one bad asset does not abort the whole sync"],"exampleFix":"// before\nfinal stream = await openOriginalStream(assetId); // throws IOException\n// after\ntry {\n  final stream = await openOriginalStream(assetId);\n  ...\n} on IOException {\n  markAssetUnreadable(assetId); // skip and continue\n}","handlingStrategy":"try-catch","validationCode":"// Kotlin: verify asset is openable before hashing\nval exists = runCatching {\n  ctx.contentResolver.openAssetFileDescriptor(uri, \"r\")?.use { true } == true\n}.getOrDefault(false)\nif (!exists) skipAsset(assetId)","typeGuard":"fun Uri.isReadable(cr: ContentResolver): Boolean =\n  runCatching { cr.openInputStream(this)?.use { true } == true }.getOrDefault(false)","tryCatchPattern":"try {\n  hashAsset(assetId)\n} catch (e: IOException) {\n  Log.w(TAG, \"unreadable asset $assetId\", e)\n  markAssetPending(assetId)\n}","preventionTips":["Re-query MediaStore existence before hashing","Request ACCESS_MEDIA_LOCATION / media permissions at runtime","Handle cloud-only (evicted) assets separately","Hash per-asset inside isolated error handling"],"tags":["android","mediastore","file-io","stream"],"backgroundTag":"file-open-failed","analyzedSha":"f48d4b332127ad365ba256108799ca8f571d2dd5","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}