{"record":{"id":"b5d9ef62aaee296c","repo":"langgenius/dify","slug":"source-workflow-must-be-published-b5d9ef","errorCode":null,"errorMessage":"source workflow must be published","messagePattern":"source workflow must be published","errorType":"http","errorClass":"BadRequest","httpStatus":400,"severity":"error","filePath":"api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py","lineNumber":712,"sourceCode":"    @setup_required\n    @login_required\n    @account_initialization_required\n    @edit_permission_required\n    @rbac_permission_required(RBACResourceScope.DATASET, RBACPermission.DATASET_EDIT)\n    @with_current_user\n    @get_rag_pipeline\n    def post(self, current_user: Account, pipeline: Pipeline, workflow_id: str):\n        rag_pipeline_service = RagPipelineService(db.session())\n\n        try:\n            workflow = rag_pipeline_service.restore_published_workflow_to_draft(\n                pipeline=pipeline,\n                workflow_id=workflow_id,\n                account=current_user,\n            )\n        except IsDraftWorkflowError as exc:\n            # Use a stable, predefined message to keep the 400 response consistent\n            raise BadRequest(RESTORE_SOURCE_WORKFLOW_MUST_BE_PUBLISHED_MESSAGE) from exc\n        except WorkflowNotFoundError as exc:\n            raise NotFound(str(exc)) from exc\n\n        return {\n            \"result\": \"success\",\n            \"hash\": workflow.unique_hash,\n            \"updated_at\": TimestampField().format(workflow.updated_at or workflow.created_at),\n        }\n\n\n@console_ns.route(\"/rag/pipelines/<uuid:pipeline_id>/workflows/<string:workflow_id>\")\nclass RagPipelineByIdApi(Resource):\n    @console_ns.response(200, \"Workflow updated successfully\", console_ns.models[WorkflowResponse.__name__])\n    @console_ns.response(400, \"No valid fields to update\")\n    @console_ns.response(403, \"Permission denied\")\n    @console_ns.response(404, \"Workflow not found\")\n    @setup_required\n    @login_required","sourceCodeStart":694,"sourceCodeEnd":730,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py#L694-L730","documentation":"Raised as BadRequest with the stable message RESTORE_SOURCE_WORKFLOW_MUST_BE_PUBLISHED_MESSAGE (\"source workflow must be published\", HTTP 400) in RagPipelineDraftWorkflowRestoreApi.post (POST /rag/pipelines/<pipeline_id>/workflows/<workflow_id>/restore). The restore operation copies a previously published workflow back into the draft slot; it can only restore from a workflow that is in the published state. IsDraftWorkflowError from the service is translated to this 400 to keep the response message stable for clients.","triggerScenarios":"POSTing to the .../restore endpoint with a workflow_id that refers to a draft workflow rather than a published one. The service's restore_published_workflow_to_draft raises IsDraftWorkflowError, which the controller maps to BadRequest.","commonSituations":"UI lets a user pick a workflow row that is actually the current draft; workflow_id taken from the draft endpoint instead of the published-workflows list; misunderstanding that restore is 'published -> draft', not 'draft -> draft'.","solutions":["Only pass a workflow_id obtained from the published workflows list (GET /rag/pipelines/<id>/workflows), not the draft endpoint.","If you are already on the draft and want to keep editing, edit it directly instead of restoring.","In the UI, disable the 'Restore to draft' action for non-published rows.","Re-fetch the workflow's state before calling restore to confirm it is published."],"exampleFix":"// before\nPOST /rag/pipelines/<id>/workflows/<draft_workflow_id>/restore   // -> 400 source workflow must be published\n// after\nPOST /rag/pipelines/<id>/workflows/<published_workflow_id>/restore","handlingStrategy":"validation","validationCode":"async function restoreWorkflow(pipelineId, workflowId) {\n  const list = await fetch(`/rag/pipelines/${pipelineId}/workflows`).then(r => r.json());\n  const wf = (list.items ?? []).find(w => w.id === workflowId);\n  if (!wf || wf.status !== 'published') throw new Error('source workflow must be published');\n  return fetch(`/rag/pipelines/${pipelineId}/workflows/${workflowId}/restore`, { method: 'POST' });\n}","typeGuard":"function isPublishableSource(wf) { return !!wf && wf.status === 'published'; }","tryCatchPattern":"try { await restoreWorkflow(id, wfId); } catch (e) { if (e.message?.includes('source workflow must be published')) pickPublishedWorkflow(); else throw e; }","preventionTips":["Only offer restore on rows from the published-workflows list.","Disable restore on draft rows in the UI.","Refresh the list before showing the restore action."],"tags":["rag-pipeline","workflow","validation","http-400"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}