diff --git a/dask/array/rechunk.py b/dask/array/rechunk.py
index 9e71b7d8e..c39ebbd97 100644
--- a/dask/array/rechunk.py
+++ b/dask/array/rechunk.py
@@ -511,7 +511,8 @@ def plan_rechunk(
         )
         if (chunks == current_chunks and not first_pass) or chunks == new_chunks:
             break
-        steps.append(chunks)
+        if chunks != current_chunks:
+            steps.append(chunks)
         current_chunks = chunks
         if not memory_limit_hit:
             break
diff --git a/dask/core.py b/dask/core.py
index 59d9fd921..153c39a4d 100644
--- a/dask/core.py
+++ b/dask/core.py
@@ -463,6 +463,6 @@ def quote(x):
     >>> quote((add, 1, 2))  # doctest: +SKIP
     (literal<type=tuple>,)
     """
-    if istask(x) or type(x) is list:
+    if istask(x) or type(x) is list or type(x) is dict:
         return (literal(x),)
     return x

plan_rechunk sometimes adds a no-op step
I noticed this in production code, but it can be demonstrated by adding a test to `dask/array/tests/test_rechunk.py::test_plan_rechunk`:

```python
    steps = _plan((c, c), (f, f), threshold=1)
    _assert_steps(steps, [(f, f)])
```
The actual plan that's recreated is [(c, c), (f, f)] i.e. the first step is to change nothing.

I'll submit a PR to avoid performing this step.
diff --git a/dask/array/tests/test_rechunk.py b/dask/array/tests/test_rechunk.py
index a3a775a7a..3ae128eee 100644
--- a/dask/array/tests/test_rechunk.py
+++ b/dask/array/tests/test_rechunk.py
@@ -280,9 +280,13 @@ def test_merge_to_number():
     assert chunks == (9,)
 
 
-def _plan(old_chunks, new_chunks, itemsize=1, block_size_limit=1e7):
+def _plan(old_chunks, new_chunks, itemsize=1, block_size_limit=1e7, threshold=4):
     return plan_rechunk(
-        old_chunks, new_chunks, itemsize=itemsize, block_size_limit=block_size_limit
+        old_chunks,
+        new_chunks,
+        itemsize=itemsize,
+        block_size_limit=block_size_limit,
+        threshold=threshold,
     )
 
 
@@ -326,6 +330,10 @@ def test_plan_rechunk():
     steps = _plan((nc + nf, c + c, c + f), (nc + nf, f + f, c + c))
     _assert_steps(steps, steps)
 
+    # Regression test for #5908
+    steps = _plan((c, c), (f, f), threshold=1)
+    _assert_steps(steps, [(f, f)])
+
     # Just at the memory limit => an intermediate is used
     steps = _plan((f, c), (c, f), block_size_limit=400)
     _assert_steps(steps, [(c, c), (c, f)])
diff --git a/dask/tests/test_core.py b/dask/tests/test_core.py
index 8ebe0acd3..1f8eacb88 100644
--- a/dask/tests/test_core.py
+++ b/dask/tests/test_core.py
@@ -250,7 +250,7 @@ def test_subs_unexpected_hashable_key():
 
 
 def test_quote():
-    literals = [[1, 2, 3], (add, 1, 2), [1, [2, 3]], (add, 1, (add, 2, 3))]
+    literals = [[1, 2, 3], (add, 1, 2), [1, [2, 3]], (add, 1, (add, 2, 3)), {"x": "x"}]
 
     for l in literals:
         assert core.get({"x": quote(l)}, "x") == l

pip install -e .[complete]
parse_log_pytest
pytest
pytest
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
bokeh==2.4.3
certifi @ file:///croot/certifi_1671487769961/work/certifi
click==8.1.8
cloudpickle==2.2.1
-e git+https://github.com/dask/dask.git@5a7f4524217d0d3ee9e44b6b6513264de7f16171#egg=dask
distributed==2.30.1
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
fsspec==2023.1.0
HeapDict==1.0.1
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
Jinja2==3.1.6
locket==1.0.0
MarkupSafe==2.1.5
msgpack==1.0.5
numpy==1.21.6
packaging @ file:///croot/packaging_1671697413597/work
pandas==1.3.5
partd==1.4.1
Pillow==9.5.0
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
psutil==7.0.0
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pytest==7.1.2
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
six==1.17.0
sortedcontainers==2.4.0
tblib==2.0.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
toolz==0.12.1
tornado==6.2
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
zict==2.2.0
zipp @ file:///croot/zipp_1672387121353/work

name: dask
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - bokeh==2.4.3
      - click==8.1.8
      - cloudpickle==2.2.1
      - distributed==2.30.1
      - fsspec==2023.1.0
      - heapdict==1.0.1
      - jinja2==3.1.6
      - locket==1.0.0
      - markupsafe==2.1.5
      - msgpack==1.0.5
      - numpy==1.21.6
      - pandas==1.3.5
      - partd==1.4.1
      - pillow==9.5.0
      - psutil==7.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - six==1.17.0
      - sortedcontainers==2.4.0
      - tblib==2.0.0
      - toolz==0.12.1
      - tornado==6.2
      - zict==2.2.0
prefix: /opt/conda/envs/dask

dask/array/tests/test_rechunk.py::test_plan_rechunk dask/tests/test_core.py::test_quote
dask/array/tests/test_rechunk.py::test_rechunk_internals_1 dask/array/tests/test_rechunk.py::test_intersect_1 dask/array/tests/test_rechunk.py::test_intersect_2 dask/array/tests/test_rechunk.py::test_rechunk_1d dask/array/tests/test_rechunk.py::test_rechunk_2d dask/array/tests/test_rechunk.py::test_rechunk_4d dask/array/tests/test_rechunk.py::test_rechunk_expand dask/array/tests/test_rechunk.py::test_rechunk_expand2 dask/array/tests/test_rechunk.py::test_rechunk_method dask/array/tests/test_rechunk.py::test_rechunk_blockshape dask/array/tests/test_rechunk.py::test_dtype dask/array/tests/test_rechunk.py::test_rechunk_with_dict dask/array/tests/test_rechunk.py::test_rechunk_with_empty_input dask/array/tests/test_rechunk.py::test_rechunk_with_null_dimensions dask/array/tests/test_rechunk.py::test_rechunk_with_integer dask/array/tests/test_rechunk.py::test_rechunk_0d dask/array/tests/test_rechunk.py::test_rechunk_empty dask/array/tests/test_rechunk.py::test_rechunk_same dask/array/tests/test_rechunk.py::test_rechunk_with_zero_placeholders dask/array/tests/test_rechunk.py::test_rechunk_minus_one dask/array/tests/test_rechunk.py::test_rechunk_intermediates dask/array/tests/test_rechunk.py::test_divide_to_width dask/array/tests/test_rechunk.py::test_merge_to_number dask/array/tests/test_rechunk.py::test_plan_rechunk_5d dask/array/tests/test_rechunk.py::test_plan_rechunk_heterogenous dask/array/tests/test_rechunk.py::test_plan_rechunk_asymmetric dask/array/tests/test_rechunk.py::test_rechunk_warning dask/array/tests/test_rechunk.py::test_dont_concatenate_single_chunks[shape0-chunks0] dask/array/tests/test_rechunk.py::test_dont_concatenate_single_chunks[shape1-chunks1] dask/array/tests/test_rechunk.py::test_dont_concatenate_single_chunks[shape2-chunks2] dask/array/tests/test_rechunk.py::test_intersect_nan dask/array/tests/test_rechunk.py::test_intersect_nan_single dask/array/tests/test_rechunk.py::test_intersect_nan_long dask/array/tests/test_rechunk.py::test_rechunk_unknown_from_pandas dask/array/tests/test_rechunk.py::test_rechunk_unknown_from_array dask/array/tests/test_rechunk.py::test_rechunk_unknown[x0-chunks0] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x1-chunks1] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x2-chunks2] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x3-chunks3] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x4-chunks4] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x5-chunks5] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x6-chunks6] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x7-chunks7] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x8-chunks8] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x9-chunks9] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x10-chunks10] dask/array/tests/test_rechunk.py::test_rechunk_unknown[x11-chunks11] dask/array/tests/test_rechunk.py::test_rechunk_unknown_explicit dask/array/tests/test_rechunk.py::test_rechunk_unknown_raises dask/array/tests/test_rechunk.py::test_old_to_new_single dask/array/tests/test_rechunk.py::test_old_to_new dask/array/tests/test_rechunk.py::test_old_to_new_large dask/array/tests/test_rechunk.py::test_changing_raises dask/array/tests/test_rechunk.py::test_old_to_new_known dask/array/tests/test_rechunk.py::test_rechunk_zero_dim dask/array/tests/test_rechunk.py::test_rechunk_empty_chunks dask/array/tests/test_rechunk.py::test_rechunk_avoid_needless_chunking dask/array/tests/test_rechunk.py::test_rechunk_auto_1d[100-1-10-expected0] dask/array/tests/test_rechunk.py::test_rechunk_auto_1d[100-50-10-expected1] dask/array/tests/test_rechunk.py::test_rechunk_auto_1d[100-100-10-expected2] dask/array/tests/test_rechunk.py::test_rechunk_auto_1d[20-7-10-expected3] dask/array/tests/test_rechunk.py::test_rechunk_auto_1d[20-chunks4-5-expected4] dask/array/tests/test_rechunk.py::test_rechunk_auto_2d dask/array/tests/test_rechunk.py::test_rechunk_auto_3d dask/array/tests/test_rechunk.py::test_rechunk_auto_image_stack[100] dask/array/tests/test_rechunk.py::test_rechunk_auto_image_stack[1000] dask/array/tests/test_rechunk.py::test_rechunk_down dask/array/tests/test_rechunk.py::test_rechunk_zero dask/array/tests/test_rechunk.py::test_rechunk_bad_keys dask/tests/test_core.py::test_istask dask/tests/test_core.py::test_has_tasks dask/tests/test_core.py::test_preorder_traversal dask/tests/test_core.py::TestGet::test_get dask/tests/test_core.py::TestGet::test_badkey dask/tests/test_core.py::TestGet::test_nested_badkey dask/tests/test_core.py::TestGet::test_data_not_in_dict_is_ok dask/tests/test_core.py::TestGet::test_get_with_list dask/tests/test_core.py::TestGet::test_get_with_list_top_level dask/tests/test_core.py::TestGet::test_get_with_nested_list dask/tests/test_core.py::TestGet::test_get_works_with_unhashables_in_values dask/tests/test_core.py::TestGet::test_nested_tasks dask/tests/test_core.py::TestGet::test_get_stack_limit dask/tests/test_core.py::TestGet::test_with_HighLevelGraph dask/tests/test_core.py::test_GetFunctionTestMixin_class dask/tests/test_core.py::test_get_dependencies_nested dask/tests/test_core.py::test_get_dependencies_empty dask/tests/test_core.py::test_get_dependencies_list dask/tests/test_core.py::test_get_dependencies_task dask/tests/test_core.py::test_get_dependencies_nothing dask/tests/test_core.py::test_get_dependencies_many dask/tests/test_core.py::test_get_dependencies_task_none dask/tests/test_core.py::test_get_deps dask/tests/test_core.py::test_flatten dask/tests/test_core.py::test_subs dask/tests/test_core.py::test_subs_no_key_data_eq dask/tests/test_core.py::test_subs_with_unfriendly_eq dask/tests/test_core.py::test_subs_with_surprisingly_friendly_eq dask/tests/test_core.py::test_subs_unexpected_hashable_key dask/tests/test_core.py::test_literal_serializable
dask/core.py dask/array/rechunk.py
dask/core.py dask/array/rechunk.py