HumanSignal/label-studio · error · RuntimeError

Export storage {self.id}: {failed} annotation(s) failed to e

Error message

Export storage {self.id}: {failed} annotation(s) failed to export

What it means

save_annotations re-raises a RuntimeError when any annotation failed to export to the target storage in its worker thread. A full sync (update_status=True) marks the storage FAILED via on_failure; partial exports (update_status=False) leave the shared status untouched but still propagate the error to the caller/RQ job.

Source

Thrown at label_studio/io_storages/base_models.py:983

                    # Resolve the future so exceptions raised inside save_annotation worker threads
                    # are not silently swallowed. Best-effort: keep exporting the rest of the batch.
                    try:
                        future.result()
                    except Exception:
                        failed += 1
                        logger.error(f'Export storage {self.id}: failed to export annotation', exc_info=True)
                        continue
                    annotation_exported += 1
                    if update_status:
                        self.info_update_progress(
                            last_sync_count=annotation_exported, total_annotations=total_annotations
                        )

        # Surface worker-thread failures by re-raising so the RQ job is marked failed. Full syncs set the
        # storage FAILED via on_failure=storage_background_failure; partial exports (update_status=False)
        # leave the shared storage status untouched, which is the intended contract.
        if failed:
            raise RuntimeError(f'Export storage {self.id}: {failed} annotation(s) failed to export')

        if update_status:
            self.info_set_completed(last_sync_count=annotation_exported, total_annotations=total_annotations)

    def save_all_annotations(self):
        self.save_annotations(Annotation.objects.filter(project=self.project))

    def save_only_new_annotations(self):
        """Do not update existing annotations, only ensure that all annotations have an ExportStorageLink"""
        # Get the storage-specific ExportStorageLink model
        storage_link_model = self.links.model
        new_annotations = Annotation.objects.filter(project=self.project).exclude(
            id__in=storage_link_model.objects.filter(storage=self, annotation__project=self.project).values(
                'annotation_id'
            )
        )
        self.save_annotations(new_annotations)

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Read the preceding per-annotation error logs (logger.exception in the worker) to find the root cause (permissions, missing bucket, network)
  2. Re-verify storage credentials with 'Validate and Import' / instance.validate_connection() and fix them in storage settings
  3. Retrying the sync (save_all_annotations) after fixing; failed annotations are retried since only successful ones were saved
  4. If partial export is intentional, wrap the call in try/except RuntimeError and treat failure count as informational

Example fix

// before
export_storage.save_only_new_annotations()  # RuntimeError aborts caller
// after
try:
    export_storage.save_only_new_annotations()
except RuntimeError as e:
    logger.warning('Partial export failure: %s', e)  # or fix creds and resync
Defensive patterns

Strategy: try-catch

Validate before calling

def can_export(storage, annotation_ids):
    try:
        storage.validate_connection()
        return True
    except Exception as e:
        logger.error('Export storage %s unreachable: %s', storage.id, e)
        return False

Try / catch

try:
    storage.save_all_annotations()
except RuntimeError as e:
    logger.error('Export sync failed: %s', e)
    # inspect per-annotation worker logs, fix storage, then resync
    storage.sync_status = 'failed'

Prevention

When it happens

Trigger: Calling save_all_annotations/save_only_new_annotations on an export storage (GCS/S3/Azure) whose target bucket credentials are invalid, bucket/prefix missing, or network unreachable, so some per-annotation uploads fail while others succeed.

Common situations: Rotated/revoked cloud credentials mid-run; bucket deleted or renamed; write permissions removed after storage was created; transient network outages during large export syncs; partial export tests expecting full success.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/f201362104a546d7. Report an issue: GitHub.