langgenius/dify · error · BadRequest
source workflow must be published
Error message
source workflow must be published
What it means
HTTP 400 BadRequest ('source workflow must be published') raised by DraftWorkflowRestoreApi.post (POST /apps/{app_id}/workflows/{workflow_id}/restore) when WorkflowService.restore_published_workflow_to_draft raises IsDraftWorkflowError. The endpoint restores a PUBLISHED workflow version back into the draft; supplying the workflow_id of a draft (not a published snapshot) is rejected. WorkflowNotFoundError maps to 404 and ValueError maps to 400 respectively in the same try block.
Source
Thrown at api/controllers/console/app/workflow.py:1552
@setup_required
@login_required
@account_initialization_required
@get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW])
@with_current_user
@edit_permission_required
@rbac_permission_required(RBACResourceScope.APP, RBACPermission.APP_RELEASE_AND_VERSION)
def post(self, current_user: Account, app_model: App, workflow_id: str):
workflow_service = WorkflowService()
try:
workflow = workflow_service.restore_published_workflow_to_draft(
app_model=app_model,
workflow_id=workflow_id,
account=current_user,
session=db.session(),
)
except IsDraftWorkflowError as exc:
raise BadRequest(RESTORE_SOURCE_WORKFLOW_MUST_BE_PUBLISHED_MESSAGE) from exc
except WorkflowNotFoundError as exc:
raise NotFound(str(exc)) from exc
except ValueError as exc:
raise BadRequest(str(exc)) from exc
return {
"result": "success",
"hash": workflow.unique_hash,
"updated_at": TimestampField().format(workflow.updated_at or workflow.created_at),
}
@console_ns.route("/apps/<uuid:app_id>/workflows/<string:workflow_id>")
class WorkflowByIdApi(Resource):
@console_ns.doc("update_workflow_by_id")
@console_ns.doc(description="Update workflow by ID")
@console_ns.doc(params={"app_id": "Application ID", "workflow_id": "Workflow ID"})
@console_ns.expect(console_ns.models[WorkflowUpdatePayload.__name__])View on GitHub (pinned to ef8544b173)
Solutions
- Use a workflow_id from the published versions list (GET /apps/{app_id}/workflows), not the draft id.
- Publish the draft first if you want its content to become restorable.
- Verify the workflow_id corresponds to a published row before calling restore.
Defensive patterns
Strategy: validation
Validate before calling
// Only call restore with ids flagged as published.
const published = await listPublishedWorkflows(appId);
if (!published.some(w => w.id === workflowId)) {
notifyUser('Select a published version to restore'); return;
} Type guard
const isPublishedWorkflow = (w): boolean => !!w && w.is_published !== false && !w.is_draft;
Try / catch
try {
return await restoreWorkflow(appId, workflowId);
} catch (e) {
if (e?.status === 400 && /source workflow must be published/i.test(e?.message)) {
promptPickPublishedVersion(); return;
}
throw e;
} Prevention
- Source workflow_id only from the published-versions list, not the drafts list.
- Publish the draft first if its content must be restorable later.
- Distinguish draft ids from published snapshot ids in the UI.
When it happens
Trigger: Calling restore with the workflow_id of the app's current draft instead of a published version id; using an id retrieved from the drafts list rather than the published-versions list.
Common situations: UI passes the wrong id (draft id) to the restore action; the user expects restore to work on an unpublished draft; confusion between Workflow.id (draft) and published snapshot ids.
Related errors
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/d33e5957e4c5bb2a.
Report an issue: GitHub.