{"record":{"id":"0dea5e8471bebbdc","repo":"HumanSignal/label-studio","slug":"conversion-to-export-type-already-started","errorCode":null,"errorMessage":"Conversion to {export_type} already started","messagePattern":"Conversion to (.+?) already started","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"warning","filePath":"label_studio/data_export/api.py","lineNumber":703,"sourceCode":")\nclass ExportConvertAPI(generics.CreateAPIView):\n    queryset = Export.objects.all()\n    lookup_url_kwarg = 'export_pk'\n    permission_required = all_permissions.projects_change\n\n    def post(self, request, *args, **kwargs):\n        snapshot = self.get_object()\n        serializer = ExportConvertSerializer(data=request.data, context={'project': snapshot.project})\n        serializer.is_valid(raise_exception=True)\n        export_type = serializer.validated_data['export_type']\n        download_resources = serializer.validated_data.get('download_resources')\n\n        converted_format, created = ConvertedFormat.objects.exclude(\n            status=ConvertedFormat.Status.FAILED\n        ).get_or_create(export=snapshot, export_type=export_type)\n\n        if not created:\n            raise ValidationError(f'Conversion to {export_type} already started')\n\n        start_job_async_or_sync(\n            async_convert,\n            converted_format.id,\n            export_type,\n            snapshot.project,\n            request.build_absolute_uri('/'),\n            download_resources=download_resources,\n            on_failure=set_convert_background_failure,\n        )\n        return Response({'export_type': export_type, 'converted_format': converted_format.id})\n","sourceCodeStart":685,"sourceCodeEnd":715,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/data_export/api.py#L685-L715","documentation":"The convert endpoint post() uses get_or_create on ConvertedFormat excluding FAILED records; if a matching non-failed ConvertedFormat already exists (created == False), a conversion for that export_type was already started or completed, so it raises ValidationError 'Conversion to {export_type} already started' to prevent duplicate jobs.","triggerScenarios":"POSTing the convert request twice for the same export snapshot and export_type (CSV twice), or after a previous conversion is still IN_PROGRESS or already COMPLETED.","commonSituations":"Users double-clicking the export-convert button; clients retrying with unclear feedback; an earlier conversion succeeded so a new one is unnecessary; a previous job stuck IN_PROGRESS blocking retries without cleanup.","solutions":["Don't re-POST; poll the existing ConvertedFormat status until it completes, then download via GET ?export_type=<type>.","Treat this as idempotent: catch the error and just proceed to poll/download.","If a job is stuck IN_PROGRESS indefinitely, have an admin reset/delete the ConvertedFormat row (or the FAILED ones are auto-retriable by design) and re-POST.","Add client-side disable/dedupe on the convert button while a job is pending."],"exampleFix":"// before (duplicate request)\nPOST /api/projects/1/exports/abc/convert  -> 400 already started\nPOST /api/projects/1/exports/abc/convert  -> 400 already started\n\n// after\nPOST /api/projects/1/exports/abc/convert  (once)\nGET  /api/projects/1/exports/abc?export_type=CSV  (poll until ready)","handlingStrategy":"try-catch","validationCode":"def conversion_pending(snapshot, export_type):\n    return snapshot.converted_formats.exclude(status='failed').filter(\n        export_type=export_type\n    ).exists()","typeGuard":null,"tryCatchPattern":"try:\n    trigger_conversion(snapshot_id, export_type)\nexcept ValidationError as e:\n    if 'already started' in str(e):\n        pass  # idempotent: existing job will finish; go poll status\n    else:\n        raise\nwait_for_conversion(snapshot_id, export_type)","preventionTips":["Check existing ConvertedFormat status before POSTing conversion","Disable convert buttons client-side while a job is pending","Treat 'already started' as success and proceed to polling/download","If a job is stuck IN_PROGRESS, clean up the row before retrying"],"tags":["data-export","duplicate-request","idempotency","async-job"],"backgroundTag":"duplicate-conversion-request","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}