{"record":{"id":"3820b38bf00afea0","repo":"HumanSignal/label-studio","slug":"batch-validation-errors","errorCode":null,"errorMessage":"batch_validation_errors","messagePattern":"batch_validation_errors","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/data_import/api.py","lineNumber":576,"sourceCode":"\n                custom_interface_errors = self._validate_custom_interface_prediction(project, item, prediction_index)\n                if custom_interface_errors:\n                    batch_validation_errors.extend(custom_interface_errors)\n                    continue\n\n                batch_predictions.append(\n                    Prediction(\n                        task_id=task_id,\n                        project_id=project.id,\n                        result=Prediction.prepare_prediction_result(item.get('result'), project),\n                        score=item.get('score'),\n                        model_version=item.get('model_version', 'undefined'),\n                    )\n                )\n                all_task_ids.add(task_id)\n\n            if batch_validation_errors:\n                raise ValidationError(batch_validation_errors)\n\n            # Bulk create this batch with the configured batch size\n            batch_created = Prediction.objects.bulk_create(batch_predictions, batch_size=settings.BATCH_SIZE)\n            total_created += len(batch_created)\n\n            logger.debug(\n                f'Processed batch {batch_start}-{batch_end - 1}: created {len(batch_created)} predictions '\n                f'(total so far: {total_created})'\n            )\n\n        # Update task counters for all affected tasks\n        # Only pass the unique task IDs that were actually processed\n        if all_task_ids:\n            start_job_async_or_sync(update_tasks_counters, Task.objects.filter(id__in=all_task_ids))\n\n        return Response({'created': total_created}, status=status.HTTP_201_CREATED)\n\n    def _create_legacy(self, project):","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/data_import/api.py#L558-L594","documentation":"At the end of each batch in _create_memory_efficient, accumulated per-batch prediction validation errors (invalid task references, custom interface errors, etc.) are raised together as ValidationError(batch_validation_errors). No predictions in the batch are created if any item failed validation.","triggerScenarios":"POSTing a batch of predictions to the import/predictions endpoint where one or more items fail validation (bad 'task' ID, invalid result for the project's custom interface). The whole batch is rejected with an array of error messages.","commonSituations":"Bulk imports mixing valid and invalid prediction items — the entire batch fails even though most items are fine; label-config mismatch introduced mid-export; partial migrations where some tasks were deleted before predictions were re-imported.","solutions":["Iterate the returned error array; each message identifies the failing prediction index/item","Fix or drop the offending items and resubmit the batch","Split large payloads into smaller chunks to isolate which items fail","Validate one item first (dry run) before sending the full batch","Cross-check all 'task' IDs against GET /api/projects/{id}/tasks before importing"],"exampleFix":"# before: fire full batch, get all-or-nothing failure\nrequests.post(url, json=predictions)\n# after: pre-filter to predictions whose tasks exist\ntask_ids = {t['id'] for t in get_all_tasks(project_id)}\nvalid = [p for p in predictions if p.get('task') in task_ids]\nrequests.post(url, json=valid)","handlingStrategy":"validation","validationCode":"existing_ids = {t['id'] for t in paginate(f'{LS}/api/projects/{pid}/tasks')}\nvalid = [p for p in batch if isinstance(p.get('task'), int) and p['task'] in existing_ids]\nassert len(valid) == len(batch), f'{len(batch)-len(valid)} predictions would fail batch validation'","typeGuard":"def batch_is_clean(batch: list, valid_ids: set) -> bool:\n    return all(isinstance(p.get('task'), int) and p['task'] in valid_ids for p in batch)","tryCatchPattern":"try:\n    resp = requests.post(import_url, headers=H, json=batch)\n    resp.raise_for_status()\nexcept requests.HTTPError as e:\n    errors = e.response.json()\n    if isinstance(errors, list):\n        log.error('%d/%d items rejected, resubmitting valid ones', len(errors), len(batch))","preventionTips":["Send batches in chunks so one bad item does not block a huge import","Dry-run a single item before large bulk imports","Validate custom-interface results against the project config before batching"],"tags":["django","rest-framework","predictions","batch","validation"],"backgroundTag":"batch-validation-failed","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}