diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue
index 122285db6b0a..fd94a48c0535 100644
--- a/client/src/components/Workflow/Editor/Index.vue
+++ b/client/src/components/Workflow/Editor/Index.vue
@@ -178,6 +178,7 @@ import { hide_modal } from "@/layout/modal";
 import { getAppRoot } from "@/onload/loadConfig";
 import { useScopePointerStore } from "@/stores/scopePointerStore";
 import { LastQueue } from "@/utils/promise-queue";
+import { errorMessageAsString } from "@/utils/simple-error";
 
 import { defaultPosition } from "./composables/useDefaultStepPosition";
 import { fromSimple, toSimple } from "./modules/model";
@@ -555,7 +556,15 @@ export default {
                 this.hasChanges = false;
                 await this.routeToWorkflow(newId);
             } catch (e) {
-                this.onWorkflowError("Saving workflow failed, please contact an administrator.");
+                if (create) {
+                    throw e;
+                }
+                const errorHeading = `Saving workflow as '${rename_name}' failed`;
+                this.onWorkflowError(errorHeading, errorMessageAsString(e) || "Please contact an administrator.", {
+                    Ok: () => {
+                        this.hideModal();
+                    },
+                });
             }
         },
         onSaveAs() {
@@ -617,13 +626,15 @@ export default {
                     );
                 }
             } catch (e) {
-                this.onWorkflowError("Creating workflow failed"),
-                    e || "Please contact an administrator.",
+                this.onWorkflowError(
+                    "Creating workflow failed",
+                    errorMessageAsString(e) || "Please contact an administrator.",
                     {
                         Ok: () => {
                             this.hideModal();
                         },
-                    };
+                    }
+                );
             }
         },
         nameValidate() {
diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py
index 9eb86dc3c2a6..80b399770b75 100644
--- a/lib/galaxy/managers/workflows.py
+++ b/lib/galaxy/managers/workflows.py
@@ -676,13 +676,16 @@ def update_workflow_from_raw_description(
         # Put parameters in workflow mode
         trans.workflow_building_mode = workflow_building_modes.ENABLED
         dry_run = workflow_update_options.dry_run
-        workflow, missing_tool_tups = self._workflow_from_raw_description(
-            trans,
-            raw_workflow_description,
-            workflow_update_options,
-            name=stored_workflow.name,
-            dry_run=dry_run,
-        )
+        try:
+            workflow, missing_tool_tups = self._workflow_from_raw_description(
+                trans,
+                raw_workflow_description,
+                workflow_update_options,
+                name=stored_workflow.name,
+                dry_run=dry_run,
+            )
+        except exceptions.DuplicatedIdentifierException as e:
+            raise e
 
         if missing_tool_tups and not workflow_update_options.allow_missing_tools:
             errors = []
@@ -794,29 +797,32 @@ def _workflow_from_raw_description(
                 )
                 subworkflow_id_map[key] = subworkflow
 
-        # Keep track of tools required by the workflow that are not available in
-        # the local Galaxy instance.  Each tuple in the list of missing_tool_tups
-        # will be (tool_id, tool_name, tool_version, step_id).
-        missing_tool_tups = []
-        for step_dict in self.__walk_step_dicts(data):
-            if not dry_run:
-                self.__load_subworkflows(
-                    trans, step_dict, subworkflow_id_map, workflow_state_resolution_options, dry_run=dry_run
-                )
+        try:
+            # Keep track of tools required by the workflow that are not available in
+            # the local Galaxy instance.  Each tuple in the list of missing_tool_tups
+            # will be (tool_id, tool_name, tool_version, step_id).
+            missing_tool_tups = []
+            for step_dict in self.__walk_step_dicts(data):
+                if not dry_run:
+                    self.__load_subworkflows(
+                        trans, step_dict, subworkflow_id_map, workflow_state_resolution_options, dry_run=dry_run
+                    )
 
-        module_kwds = workflow_state_resolution_options.dict()
-        module_kwds.update(kwds)  # TODO: maybe drop this?
-        for step_dict in self.__walk_step_dicts(data):
-            module, step = self.__module_from_dict(trans, steps, steps_by_external_id, step_dict, **module_kwds)
-            if isinstance(module, ToolModule) and module.tool is None:
-                missing_tool_tup = (module.tool_id, module.get_name(), module.tool_version, step_dict["id"])
-                if missing_tool_tup not in missing_tool_tups:
-                    missing_tool_tups.append(missing_tool_tup)
-            if module.get_errors():
-                workflow.has_errors = True
-
-        # Second pass to deal with connections between steps
-        self.__connect_workflow_steps(steps, steps_by_external_id, dry_run)
+            module_kwds = workflow_state_resolution_options.dict()
+            module_kwds.update(kwds)  # TODO: maybe drop this?
+            for step_dict in self.__walk_step_dicts(data):
+                module, step = self.__module_from_dict(trans, steps, steps_by_external_id, step_dict, **module_kwds)
+                if isinstance(module, ToolModule) and module.tool is None:
+                    missing_tool_tup = (module.tool_id, module.get_name(), module.tool_version, step_dict["id"])
+                    if missing_tool_tup not in missing_tool_tups:
+                        missing_tool_tups.append(missing_tool_tup)
+                if module.get_errors():
+                    workflow.has_errors = True
+
+            # Second pass to deal with connections between steps
+            self.__connect_workflow_steps(steps, steps_by_external_id, dry_run)
+        except exceptions.DuplicatedIdentifierException as e:
+            raise e
 
         workflow.has_cycles = True
         workflow.steps = steps
diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py
index 8cf8d5f629bc..f84376efc7c0 100644
--- a/lib/galaxy/webapps/galaxy/controllers/workflow.py
+++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py
@@ -11,6 +11,7 @@
 from sqlalchemy.sql import expression
 
 from galaxy import (
+    exceptions,
     model,
     util,
     web,
@@ -416,11 +417,6 @@ def save_workflow_as(
             workflow_annotation = sanitize_html(workflow_annotation)
             self.add_item_annotation(trans.sa_session, trans.get_user(), stored_workflow, workflow_annotation)
 
-            # Persist
-            session = trans.sa_session
-            session.add(stored_workflow)
-            with transaction(session):
-                session.commit()
             workflow_update_options = WorkflowUpdateOptions(
                 update_stored_workflow_attributes=False,  # taken care of above
                 from_tool_form=from_tool_form,
@@ -432,8 +428,10 @@ def save_workflow_as(
                     workflow_data,
                     workflow_update_options,
                 )
+            except exceptions.DuplicatedIdentifierException as e:
+                return self.message_exception(trans, e.err_msg, False)
             except MissingToolsException as e:
-                return dict(
+                missing_tools_return = dict(
                     name=e.workflow.name,
                     message=(
                         "This workflow includes missing or invalid tools. "
@@ -441,6 +439,18 @@ def save_workflow_as(
                     ),
                     errors=e.errors,
                 )
+            else:
+                missing_tools_return = None
+
+            # Persist
+            session = trans.sa_session
+            session.add(stored_workflow)
+            with transaction(session):
+                session.commit()
+
+            if missing_tools_return:
+                return missing_tools_return
+
             return trans.security.encode_id(stored_workflow.id)
         else:
             # This is an error state, 'save as' must have a workflow_name