diff --git a/sceptre/cli/update.py b/sceptre/cli/update.py
index 56c9b242f..53c93d386 100644
--- a/sceptre/cli/update.py
+++ b/sceptre/cli/update.py
@@ -57,14 +57,30 @@ def update_command(ctx, path, change_set, verbose, yes):
         try:
             # Wait for change set to be created
             statuses = plan.wait_for_cs_completion(change_set_name)
-            # Exit if change set fails to create
+
+            at_least_one_ready = False
+
             for status in list(statuses.values()):
-                if status != StackChangeSetStatus.READY:
+                # Exit if change set fails to create
+                if status not in (StackChangeSetStatus.READY, StackChangeSetStatus.NO_CHANGES):
+                    write("Failed to create change set", context.output_format)
                     exit(1)
 
+                if status == StackChangeSetStatus.READY:
+                    at_least_one_ready = True
+
+            # If none are ready, and we haven't exited, there are no changes
+            if not at_least_one_ready:
+                write("No changes detected", context.output_format)
+                exit(0)
+
             # Describe changes
             descriptions = plan.describe_change_set(change_set_name)
-            for description in list(descriptions.values()):
+            for stack, description in descriptions.items():
+                # No need to print if there are no changes
+                if statuses[stack] == StackChangeSetStatus.NO_CHANGES:
+                    continue
+
                 if not verbose:
                     description = simplify_change_set_description(description)
                 write(description, context.output_format)
@@ -72,8 +88,7 @@ def update_command(ctx, path, change_set, verbose, yes):
             # Execute change set if happy with changes
             if yes or click.confirm("Proceed with stack update?"):
                 plan.execute_change_set(change_set_name)
-        except Exception as e:
-            raise e
+
         finally:
             # Clean up by deleting change set
             plan.delete_change_set(change_set_name)
diff --git a/sceptre/plan/actions.py b/sceptre/plan/actions.py
index d476ec0bb..61f78e434 100644
--- a/sceptre/plan/actions.py
+++ b/sceptre/plan/actions.py
@@ -884,6 +884,7 @@ def _get_cs_status(self, change_set_name):
         cs_description = self.describe_change_set(change_set_name)
 
         cs_status = cs_description["Status"]
+        cs_reason = cs_description.get("StatusReason")
         cs_exec_status = cs_description["ExecutionStatus"]
         possible_statuses = [
             "CREATE_PENDING", "CREATE_IN_PROGRESS",
@@ -915,6 +916,12 @@ def _get_cs_status(self, change_set_name):
                 cs_exec_status in ["UNAVAILABLE", "AVAILABLE"]
         ):
             return StackChangeSetStatus.PENDING
+        elif (
+                cs_status == "FAILED" and
+                cs_reason is not None and
+                self.change_set_creation_failed_due_to_no_changes(cs_reason)
+        ):
+            return StackChangeSetStatus.NO_CHANGES
         elif (
                 cs_status in ["DELETE_COMPLETE", "FAILED"] or
                 cs_exec_status in [
diff --git a/sceptre/stack_status.py b/sceptre/stack_status.py
index 67bddb1da..65e63f65d 100644
--- a/sceptre/stack_status.py
+++ b/sceptre/stack_status.py
@@ -25,3 +25,4 @@ class StackChangeSetStatus(object):
     PENDING = "pending"
     READY = "ready"
     DEFUNCT = "defunct"
+    NO_CHANGES = "no changes"
diff --git a/tests/test_actions.py b/tests/test_actions.py
index 9c569bb75..4ed0575b6 100644
--- a/tests/test_actions.py
+++ b/tests/test_actions.py
@@ -1088,6 +1088,17 @@ def test_get_cs_status_handles_all_statuses(
                 )
                 assert response == returns[i]
 
+        mock_describe_change_set.return_value = {
+            "Status": "FAILED",
+            "StatusReason": "The submitted information didn't contain changes. "
+                            "Submit different information to create a change set.",
+            "ExecutionStatus": "UNAVAILABLE"
+        }
+        response = self.actions._get_cs_status(
+            sentinel.change_set_name
+        )
+        assert response == scss.NO_CHANGES
+
         for status in return_values['Status']:
             mock_describe_change_set.return_value = {
                 "Status": status,
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 5d0dde359..97b80962f 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -21,7 +21,7 @@
 from sceptre.exceptions import SceptreException
 from sceptre.plan.actions import StackActions
 from sceptre.stack import Stack
-from sceptre.stack_status import StackStatus
+from sceptre.stack_status import StackChangeSetStatus, StackStatus
 
 
 class TestCli(object):
@@ -484,6 +484,92 @@ def test_stack_commands(self, command, success, yes_flag, exit_code):
         run_command.assert_called_with()
         assert result.exit_code == exit_code
 
+    @pytest.mark.parametrize(
+        "change_set_status,yes_flag,exit_code,verbose_flag", [
+            (StackChangeSetStatus.READY, True, 0, True),
+            (StackChangeSetStatus.READY, True, 0, False),
+            (StackChangeSetStatus.READY, False, 0, True),
+            (StackChangeSetStatus.READY, False, 0, False),
+            (StackChangeSetStatus.NO_CHANGES, True, 0, True),
+            (StackChangeSetStatus.NO_CHANGES, False, 0, False),
+            (StackChangeSetStatus.NO_CHANGES, True, 0, False),
+            (StackChangeSetStatus.NO_CHANGES, False, 0, True),
+            (StackChangeSetStatus.DEFUNCT, True, 1, True),
+            (StackChangeSetStatus.DEFUNCT, False, 1, False),
+            (StackChangeSetStatus.DEFUNCT, True, 1, False),
+            (StackChangeSetStatus.DEFUNCT, False, 1, True),
+        ]
+    )
+    def test_update_with_change_set(self, change_set_status, yes_flag, exit_code, verbose_flag):
+        create_command = self.mock_stack_actions.create_change_set
+        wait_command = self.mock_stack_actions.wait_for_cs_completion
+        execute_command = self.mock_stack_actions.execute_change_set
+        delete_command = self.mock_stack_actions.delete_change_set
+        describe_command = self.mock_stack_actions.describe_change_set
+
+        wait_command.return_value = change_set_status
+
+        response = {
+            "VerboseProperty": "VerboseProperty",
+            "ChangeSetName": "ChangeSetName",
+            "CreationTime": "CreationTime",
+            "ExecutionStatus": "ExecutionStatus",
+            "StackName": "StackName",
+            "Status": "Status",
+            "StatusReason": "StatusReason",
+            "Changes": [
+                {
+                    "ResourceChange": {
+                        "Action": "Action",
+                        "LogicalResourceId": "LogicalResourceId",
+                        "PhysicalResourceId": "PhysicalResourceId",
+                        "Replacement": "Replacement",
+                        "ResourceType": "ResourceType",
+                        "Scope": "Scope",
+                        "VerboseProperty": "VerboseProperty"
+                    }
+                }
+            ]
+        }
+
+        if not verbose_flag:
+            del response["VerboseProperty"]
+            del response["Changes"][0]["ResourceChange"]["VerboseProperty"]
+
+        describe_command.return_value = response
+
+        kwargs = {"args": ["update", "--change-set", "dev/vpc.yaml"]}
+
+        if yes_flag:
+            kwargs["args"].append("-y")
+        else:
+            kwargs["input"] = "y\n"
+
+        if verbose_flag:
+            kwargs["args"].append("-v")
+
+        result = self.runner.invoke(cli, **kwargs)
+
+        change_set_name = create_command.call_args[0][0]
+        assert 'change-set' in change_set_name
+
+        assert wait_command.called_with(change_set_name)
+        assert delete_command.called_with(change_set_name)
+
+        if change_set_status == StackChangeSetStatus.READY:
+            assert execute_command.called_with(change_set_name)
+            assert describe_command.called_with(change_set_name)
+            output = result.output.splitlines()[0]
+            assert yaml.safe_load(output) == response
+
+        if change_set_status == StackChangeSetStatus.DEFUNCT:
+            assert "Failed to create change set" in result.output
+
+        if change_set_status == StackChangeSetStatus.NO_CHANGES:
+            assert "No changes detected" in result.output
+
+        assert result.exit_code == exit_code
+
     @pytest.mark.parametrize(
         "command, ignore_dependencies", [
             ("create", True),