{"record":{"id":"a500678f884d491b","repo":"HumanSignal/label-studio","slug":"extract-message-exc","errorCode":null,"errorMessage":"extract_message(exc)","messagePattern":"extract_message\\(exc\\)","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/io_storages/gcs/serializers.py","lineNumber":42,"sourceCode":"            result.pop(attr)\n        return result\n\n    def validate(self, data):\n        data = super().validate(data)\n        storage = self.instance\n        if storage:\n            for key, value in data.items():\n                setattr(storage, key, value)\n        else:\n            if 'id' in self.initial_data:\n                storage_object = self.Meta.model.objects.get(id=self.initial_data['id'])\n                for attr in GCSImportStorageSerializer.secure_fields:\n                    data[attr] = data.get(attr) or getattr(storage_object, attr)\n            storage = self.Meta.model(**data)\n        try:\n            storage.validate_connection()\n        except Exception as exc:\n            raise ValidationError(extract_message(exc))\n        return data\n\n\nclass GCSExportStorageSerializer(ExportStorageSerializer):\n    type = StorageTypeField(default=os.path.basename(os.path.dirname(__file__)))\n\n    def to_representation(self, instance):\n        result = super().to_representation(instance)\n        result.pop('google_application_credentials')\n        return result\n\n    class Meta:\n        model = GCSExportStorage\n        fields = '__all__'\n","sourceCodeStart":24,"sourceCodeEnd":57,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/io_storages/gcs/serializers.py#L24-L57","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the extract_message text in the API error response — it names the real GCS failure (auth, 404 bucket, permission)","Confirm the credentials JSON parses locally (python -m json.tool) and that private_key newlines are \\n-escaped when passed as a string","Verify the bucket exists and the service account has roles/storage.objectViewer and objectCreator (or objectAdmin)","Test from the server: gsutil ls gs://<bucket> or gcs.Client(...).get_bucket(bucket) to reproduce outside Label Studio"],"exampleFix":"// before\n{\"google_application_credentials\": \"/local/path/key.json\"}  // path, not contents\n// after (paste the JSON contents, properly escaped)\n{\"google_application_credentials\": \"{\\\"type\\\": \\\"service_account\\\", ... }\", \"bucket\": \"my-bucket\"}","handlingStrategy":"try-catch","validationCode":"import json\ndef gcs_credentials_ok(creds_str, bucket):\n    try:\n        json.loads(creds_str)\n    except json.JSONDecodeError:\n        return False, 'credentials are not valid JSON'\n    from google.cloud import storage\n    try:\n        storage.Client.from_service_account_json.__self__  # import sanity\n        c = storage.Client.from_service_account_info(json.loads(creds_str))\n        c.get_bucket(bucket)\n        return True, None\n    except Exception as e:\n        return False, str(e)","typeGuard":"def is_json_string(s):\n    import json\n    if not isinstance(s, str):\n        return False\n    try:\n        json.loads(s)\n        return True\n    except json.JSONDecodeError:\n        return False","tryCatchPattern":"from rest_framework.exceptions import ValidationError\ntry:\n    api.save_gcs_storage(payload)\nexcept ValidationError as e:\n    logger.error('GCS storage rejected: %s', e.detail)  # extract_message shows the real GCS error","preventionTips":["Paste credentials file CONTENTS, not the path","Validate with json.loads before submitting","Check private_key \\n escaping survives form submission","Pre-verify bucket access with gsutil ls or get_bucket"],"tags":["gcs","google-cloud","validation","drf"],"backgroundTag":"gcs-connection-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}