HumanSignal/label-studio · error · ValidationError

extract_message(exc)

Error message

extract_message(exc)

What it means

In GCSImportStorageSerializer.validate (and the export analog), after building the storage instance it calls storage.validate_connection(); any exception is converted to a DRF ValidationError whose message is extract_message(exc) — so the client sees the underlying Google client error text (auth, bucket not found, etc.) as field errors.

Source

Thrown at label_studio/io_storages/gcs/serializers.py:42

            result.pop(attr)
        return result

    def validate(self, data):
        data = super().validate(data)
        storage = self.instance
        if storage:
            for key, value in data.items():
                setattr(storage, key, value)
        else:
            if 'id' in self.initial_data:
                storage_object = self.Meta.model.objects.get(id=self.initial_data['id'])
                for attr in GCSImportStorageSerializer.secure_fields:
                    data[attr] = data.get(attr) or getattr(storage_object, attr)
            storage = self.Meta.model(**data)
        try:
            storage.validate_connection()
        except Exception as exc:
            raise ValidationError(extract_message(exc))
        return data


class GCSExportStorageSerializer(ExportStorageSerializer):
    type = StorageTypeField(default=os.path.basename(os.path.dirname(__file__)))

    def to_representation(self, instance):
        result = super().to_representation(instance)
        result.pop('google_application_credentials')
        return result

    class Meta:
        model = GCSExportStorage
        fields = '__all__'

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Read the extract_message text in the API error response — it names the real GCS failure (auth, 404 bucket, permission)
  2. Confirm the credentials JSON parses locally (python -m json.tool) and that private_key newlines are \n-escaped when passed as a string
  3. Verify the bucket exists and the service account has roles/storage.objectViewer and objectCreator (or objectAdmin)
  4. Test from the server: gsutil ls gs://<bucket> or gcs.Client(...).get_bucket(bucket) to reproduce outside Label Studio

Example fix

// before
{"google_application_credentials": "/local/path/key.json"}  // path, not contents
// after (paste the JSON contents, properly escaped)
{"google_application_credentials": "{\"type\": \"service_account\", ... }", "bucket": "my-bucket"}
Defensive patterns

Strategy: try-catch

Validate before calling

import json
def gcs_credentials_ok(creds_str, bucket):
    try:
        json.loads(creds_str)
    except json.JSONDecodeError:
        return False, 'credentials are not valid JSON'
    from google.cloud import storage
    try:
        storage.Client.from_service_account_json.__self__  # import sanity
        c = storage.Client.from_service_account_info(json.loads(creds_str))
        c.get_bucket(bucket)
        return True, None
    except Exception as e:
        return False, str(e)

Type guard

def is_json_string(s):
    import json
    if not isinstance(s, str):
        return False
    try:
        json.loads(s)
        return True
    except json.JSONDecodeError:
        return False

Try / catch

from rest_framework.exceptions import ValidationError
try:
    api.save_gcs_storage(payload)
except ValidationError as e:
    logger.error('GCS storage rejected: %s', e.detail)  # extract_message shows the real GCS error

Prevention

When it happens

Trigger: Saving a GCS import/export storage via the API with invalid service-account credentials JSON, a nonexistent bucket, insufficient IAM permissions, or unreachable network — anything that makes validate_connection() raise.

Common situations: Pasting the whole Google credentials JSON with wrong escaping (newlines in private_key); bucket renamed/deleted; service account missing storage.buckets.get; workload without outbound access to storage.googleapis.com.

Related errors


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