{"record":{"id":"b3cffea2e716142f","repo":"immich-app/immich","slug":"failed-to-get-sync-stream-errorbody","errorCode":null,"errorMessage":"Failed to get sync stream: $errorBody","messagePattern":"Failed to get sync stream: \\$errorBody","errorType":"http","errorClass":"ApiException","httpStatus":null,"severity":"error","filePath":"mobile/lib/infrastructure/repositories/sync_api.repository.dart","lineNumber":89,"sourceCode":"\n    String previousChunk = '';\n    final List<String> lines = [];\n\n    bool shouldAbort = false;\n\n    void abort() {\n      _logger.warning(\"Abort requested, stopping sync stream\");\n      shouldAbort = true;\n    }\n\n    final reset = onReset ?? () {};\n\n    try {\n      final response = await client.send(request);\n\n      if (response.statusCode != 200) {\n        final errorBody = await response.stream.bytesToString();\n        throw ApiException(response.statusCode, 'Failed to get sync stream: $errorBody');\n      }\n\n      await for (final chunk in response.stream.transform(utf8.decoder)) {\n        if (shouldAbort) {\n          break;\n        }\n\n        previousChunk += chunk;\n        final parts = previousChunk.split('\\n');\n        previousChunk = parts.removeLast();\n        lines.addAll(parts);\n\n        if (lines.length < batchSize) {\n          continue;\n        }\n\n        await onData(_parseLines(lines), abort, reset);\n        lines.clear();","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/lib/infrastructure/repositories/sync_api.repository.dart#L71-L107","documentation":"The sync-stream repository opens an HTTP request and expects status 200. Any other status aborts the stream before reading: the error body is drained and wrapped in an ApiException carrying the status code and the raw response body under the message 'Failed to get sync stream: ...'.","triggerScenarios":"Server responds with non-200 (401 expired token, 403, 500, 502) to the sync-stream request; server endpoint missing or a proxy intercepts the stream request.","commonSituations":"Access token expired between login and sync; server upgraded/changed API so the sync endpoint errors; reverse proxy or CDN rejects long-lived streaming requests; server error while checkpointing.","solutions":["Log the included status code and error body to identify the server-side cause","Refresh authentication (re-login / token renewal) on 401/403 before retrying","Retry with backoff on 5xx / transient gateway errors","Confirm the server version supports the sync-stream endpoint"],"exampleFix":"// before\nfinal delta = await syncApi.getSyncStream(...); // throws raw ApiException\n// after\ntry {\n  await syncApi.getSyncStream(...);\n} on ApiException catch (e) {\n  if (e.code == 401) await reauthenticate();\n  else if (e.code >= 500) scheduleRetryWithBackoff();\n  rethrow;\n}","handlingStrategy":"try-catch","validationCode":"// Dart: sanity-check session before opening the stream\nif (accessToken == null || serverUrl == null) {\n  throw StateError('Not authenticated for sync stream');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await for (final chunk in syncApi.getSyncStream(...)) { ... }\n} on ApiException catch (e) {\n  if (e.code == 401 || e.code == 403) {\n    await reauthenticate();\n  } else if (e.code >= 500) {\n    retryWithBackoff();\n  }\n}","preventionTips":["Refresh tokens proactively before long sync sessions","Verify server version supports the sync-stream endpoint","Check proxy/CDN timeouts for streaming responses","Log status + body from ApiException for diagnosis"],"tags":["dart","http","streaming","server-error"],"backgroundTag":"http-error-response","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}