Repository: PrefectHQ/prefect

Issue:
Diagnostics displays values that do not differ from defaults
## Current behavior
<!-- Please describe how the feature works today -->

If you copy the `prefect/src/prefect/config.toml` file to a local copy at `~/.prefect/config.toml` to easily have all the settings available, `prefect diagnostics` will display all of the settings as non-default even if they have not been changed.

## Proposed behavior
<!-- Please describe your proposed change to the current behavior -->

Resolving the differences between your `config.toml` and the built-in config should be relatively straightforward and would display more helpful diagnostic information.

## Example
<!-- Please give an example of how the enhancement would be useful -->

- User copies config file
- User posts diagnostics
- Support gets confused about all the non-defaults and cannot find what has actually changed

vs

- Only truly changed values are displayed
- Support has a really nice day

Discussion:

Patch:
diff --git a/changes/pr3589.yaml b/changes/pr3589.yaml
deleted file mode 100644
index 795f597801..0000000000
--- a/changes/pr3589.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-enhancement:
-  - "Display exception information on `prefect create project` failure - [#3589](https://github.com/PrefectHQ/prefect/pull/3589)"
diff --git a/changes/pr3593.yaml b/changes/pr3593.yaml
new file mode 100644
index 0000000000..e65bcec38a
--- /dev/null
+++ b/changes/pr3593.yaml
@@ -0,0 +1,2 @@
+enhancement:
+  - "`prefect diagnostics` no longer displays keys that have values matching the default config - [#3593](https://github.com/PrefectHQ/prefect/pull/3593)"
diff --git a/src/prefect/cli/create.py b/src/prefect/cli/create.py
index 568586ccd1..eba920257f 100644
--- a/src/prefect/cli/create.py
+++ b/src/prefect/cli/create.py
@@ -46,8 +46,7 @@ def project(name, description):
     """
     try:
         Client().create_project(project_name=name, project_description=description)
-    except ClientError as exc:
-        click.echo(f"{type(exc).__name__}: {exc}")
+    except ClientError:
         click.secho("Error creating project", fg="red")
         return
 
diff --git a/src/prefect/utilities/collections.py b/src/prefect/utilities/collections.py
index 048d2cfd7f..4eee035892 100644
--- a/src/prefect/utilities/collections.py
+++ b/src/prefect/utilities/collections.py
@@ -1,10 +1,11 @@
 import collections
 from collections.abc import MutableMapping
-from typing import Any, Generator, Iterable, Iterator, Union, cast
+from typing import Any, Generator, Iterable, Iterator, Union, cast, TypeVar, Type
 
 from box import Box
 
 DictLike = Union[dict, "DotDict"]
+D = TypeVar("D", bound=Union[dict, MutableMapping])
 
 
 def flatten_seq(seq: Iterable) -> Generator:
@@ -181,7 +182,7 @@ class CompoundKey(tuple):
     pass
 
 
-def dict_to_flatdict(dct: dict, parent: CompoundKey = None) -> dict:
+def dict_to_flatdict(dct: DictLike, parent: CompoundKey = None) -> dict:
     """Converts a (nested) dictionary to a flattened representation.
 
     Each key of the flat dict will be a CompoundKey tuple containing the "chain of keys"
@@ -207,19 +208,19 @@ def dict_to_flatdict(dct: dict, parent: CompoundKey = None) -> dict:
     return dict(items)
 
 
-def flatdict_to_dict(dct: dict, dct_class: type = None) -> MutableMapping:
+def flatdict_to_dict(dct: dict, dct_class: Type[D] = None) -> D:
     """Converts a flattened dictionary back to a nested dictionary.
 
     Args:
         - dct (dict): The dictionary to be nested. Each key should be a
-        `CompoundKey`, as generated by `dict_to_flatdict()`
+            `CompoundKey`, as generated by `dict_to_flatdict()`
         - dct_class (type, optional): the type of the result; defaults to `dict`
 
     Returns:
-        - MutableMapping: A `MutableMapping` used to represent a nested dictionary
+        - D: An instance of `dct_class` used to represent a nested dictionary, bounded
+            as a MutableMapping or dict
     """
-
-    result = (dct_class or dict)()  # type: MutableMapping
+    result = cast(D, (dct_class or dict)())
     for k, v in dct.items():
         if isinstance(k, CompoundKey):
             current_dict = result
diff --git a/src/prefect/utilities/diagnostics.py b/src/prefect/utilities/diagnostics.py
index e02673b1b7..16211fe21f 100644
--- a/src/prefect/utilities/diagnostics.py
+++ b/src/prefect/utilities/diagnostics.py
@@ -25,11 +25,14 @@ def system_information() -> dict:
 
 def config_overrides(include_secret_names: bool = False) -> dict:
     """
-    Get user configuration overrides
+    Get user configuration keys that differ from the default configuration. Will only
+    return an indication if a key is set and differs from the defaults, values are
+    *not* returned.
 
     Args:
-        - include_secret_names (bool, optional): toggle output of Secret names, defaults to False.
-            Note: Secret values are never returned, only their names.
+        - include_secret_names (bool, optional): toggle inclusion of secret config keys
+            in the output. Note that secret values are never returned, only their names
+            when this is `True`. Defaults to `False`.
 
     Returns:
         - dict: a dictionary containing names of user configuration overrides
@@ -45,13 +48,42 @@ def config_overrides(include_secret_names: bool = False) -> dict:
             }
         return True
 
+    # Load the default config to compare values
+    default_config = dict()
+    default_config_path = prefect.configuration.DEFAULT_CONFIG
+    if default_config_path and os.path.isfile(default_config_path):
+        default_config = prefect.configuration.load_toml(default_config_path)
+
     user_config = dict()  # type: ignore
     user_config_path = prefect.configuration.USER_CONFIG
     if user_config_path and os.path.isfile(
         str(prefect.configuration.interpolate_env_vars(user_config_path))
     ):
         user_config = prefect.configuration.load_toml(user_config_path)
-        user_config = _replace_values(user_config)
+
+    # Create some shorter names for fully specified imports avoiding circular
+    # dependencies in the utilities
+    dict_to_flatdict = prefect.utilities.collections.dict_to_flatdict
+    flatdict_to_dict = prefect.utilities.collections.flatdict_to_dict
+
+    # Drop keys from `user_config` that have values identical to `default_config`
+    # converting to flat dictionaries for ease of comparison
+    user_config = dict_to_flatdict(user_config)
+    default_config = dict_to_flatdict(default_config)
+
+    # Collect keys to drop in a list so we aren't dropping keys during iteration
+    keys_to_drop = [
+        key
+        for key, val in user_config.items()
+        if key in default_config and val == default_config[key]
+    ]
+
+    for key in keys_to_drop:
+        user_config.pop(key)
+
+    # Restore to a nested dictionary then replace values with bools
+    user_config = flatdict_to_dict(user_config)
+    user_config = _replace_values(user_config)
 
     return dict(config_overrides=user_config)
 


Test patch:
diff --git a/tests/utilities/test_diagnostics.py b/tests/utilities/test_diagnostics.py
index b52d1daf04..f86cb74d4f 100644
--- a/tests/utilities/test_diagnostics.py
+++ b/tests/utilities/test_diagnostics.py
@@ -38,6 +38,39 @@ def test_config_overrides_populated(monkeypatch):
         assert config_overrides["config_overrides"] == {"debug": True}
 
 
+def test_config_overrides_excludes_all_default_matches(monkeypatch):
+    monkeypatch.setattr(
+        "prefect.configuration.USER_CONFIG", prefect.configuration.DEFAULT_CONFIG
+    )
+
+    config_overrides = diagnostics.config_overrides()
+
+    assert config_overrides["config_overrides"] == {}
+
+
+def test_config_overrides_excludes_some_default_matches(monkeypatch, tmpdir):
+    # Load and modify the default config
+    default_config = prefect.configuration.load_toml(
+        prefect.configuration.DEFAULT_CONFIG
+    )
+    default_config["debug"] = True
+    default_config["cloud"]["agent"]["name"] = "foo"
+
+    # Write it as a new user config
+    user_config_path = str(tmpdir.join("config.toml"))
+    file = open(user_config_path, "w+")
+    toml.dump(default_config, file)
+    file.close()
+    monkeypatch.setattr("prefect.configuration.USER_CONFIG", user_config_path)
+
+    config_overrides = diagnostics.config_overrides()
+
+    assert config_overrides["config_overrides"] == {
+        "debug": True,
+        "cloud": {"agent": {"name": True}},
+    }
+
+
 def test_config_overrides_secrets(monkeypatch):
     with tempfile.TemporaryDirectory() as tempdir:
         file = open("{}/config.toml".format(tempdir), "w+")