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

  1. Use a workflow_id from the published versions list (GET /apps/{app_id}/workflows), not the draft id.
  2. Publish the draft first if you want its content to become restorable.
  3. 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

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.