diff --git a/dask/bag/core.py b/dask/bag/core.py
index bc79ee350..d8bf1c6eb 100644
--- a/dask/bag/core.py
+++ b/dask/bag/core.py
@@ -1970,7 +1970,7 @@ def groupby_tasks(b, grouper, hash=hash, max_branch=32):
     max_branch = max_branch or 32
     n = b.npartitions
 
-    stages = int(math.ceil(math.log(n) / math.log(max_branch)))
+    stages = int(math.ceil(math.log(n) / math.log(max_branch))) or 1
     if stages > 1:
         k = int(math.ceil(n ** (1 / stages)))
     else:

Bag groupby error using tasks based shuffle
Looks like the issue is when the bag in question has only 1 partition.

```
>>> dask.bag.from_sequence('abc', npartitions = 1).groupby(lambda x: x, shuffle = 'tasks').compute()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/dask/bag/core.py", line 1240, in groupby
    return groupby_tasks(self, grouper, max_branch=max_branch)
  File "/usr/local/lib/python3.6/dist-packages/dask/bag/core.py", line 2012, in groupby_tasks
    for i, j in enumerate(join))
UnboundLocalError: local variable 'join' referenced before assignment
```

Dask version 0.18.1
diff --git a/dask/bag/tests/test_bag.py b/dask/bag/tests/test_bag.py
index fa93e5956..1c214314d 100644
--- a/dask/bag/tests/test_bag.py
+++ b/dask/bag/tests/test_bag.py
@@ -1138,7 +1138,8 @@ def test_groupby_tasks_names():
 
 
 @pytest.mark.parametrize('size,npartitions,groups', [(1000, 20, 100),
-                                                     (12345, 234, 1042)])
+                                                     (12345, 234, 1042),
+                                                     (100, 1, 50)])
 def test_groupby_tasks_2(size, npartitions, groups):
     func = lambda x: x % groups
     b = db.range(size, npartitions=npartitions).groupby(func, shuffle='tasks')

pip install -e .[complete]
parse_log_pytest
pytest
pytest pytest-xdist pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
click==8.1.8
cloudpickle==3.1.1
coverage==7.8.0
-e git+https://github.com/dask/dask.git@df1cee3b55706443303b85563e7c01e26611603d#egg=dask
distributed==1.28.1
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
execnet==2.1.1
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
locket==1.0.0
msgpack==1.1.0
numpy==2.0.2
packaging @ file:///croot/packaging_1734472117206/work
pandas==2.2.3
partd==1.4.2
pluggy @ file:///croot/pluggy_1733169602837/work
psutil==7.0.0
pytest @ file:///croot/pytest_1738938843180/work
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.2
six==1.17.0
sortedcontainers==2.4.0
tblib==3.1.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
toolz==1.0.0
tornado==6.4.2
tzdata==2025.2
zict==3.0.0

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
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_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=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - click==8.1.8
      - cloudpickle==3.1.1
      - coverage==7.8.0
      - distributed==1.28.1
      - execnet==2.1.1
      - locket==1.0.0
      - msgpack==1.1.0
      - numpy==2.0.2
      - pandas==2.2.3
      - partd==1.4.2
      - psutil==7.0.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.2
      - six==1.17.0
      - sortedcontainers==2.4.0
      - tblib==3.1.0
      - toolz==1.0.0
      - tornado==6.4.2
      - tzdata==2025.2
      - zict==3.0.0
prefix: /opt/conda/envs/dask

dask/bag/tests/test_bag.py::test_groupby_tasks_2[100-1-50]
dask/bag/tests/test_bag.py::test_take_npartitions_warn dask/bag/tests/test_bag.py::test_to_dataframe dask/bag/tests/test_bag.py::test_to_textfiles_name_function_warn
dask/bag/tests/test_bag.py::test_Bag dask/bag/tests/test_bag.py::test_keys dask/bag/tests/test_bag.py::test_bag_map dask/bag/tests/test_bag.py::test_map_method dask/bag/tests/test_bag.py::test_starmap dask/bag/tests/test_bag.py::test_filter dask/bag/tests/test_bag.py::test_remove dask/bag/tests/test_bag.py::test_iter dask/bag/tests/test_bag.py::test_repr[str] dask/bag/tests/test_bag.py::test_repr[repr] dask/bag/tests/test_bag.py::test_pluck dask/bag/tests/test_bag.py::test_pluck_with_default dask/bag/tests/test_bag.py::test_unzip dask/bag/tests/test_bag.py::test_fold dask/bag/tests/test_bag.py::test_distinct dask/bag/tests/test_bag.py::test_frequencies dask/bag/tests/test_bag.py::test_frequencies_sorted dask/bag/tests/test_bag.py::test_topk dask/bag/tests/test_bag.py::test_topk_with_non_callable_key[1] dask/bag/tests/test_bag.py::test_topk_with_non_callable_key[2] dask/bag/tests/test_bag.py::test_topk_with_multiarg_lambda dask/bag/tests/test_bag.py::test_lambdas dask/bag/tests/test_bag.py::test_reductions dask/bag/tests/test_bag.py::test_reduction_names dask/bag/tests/test_bag.py::test_tree_reductions dask/bag/tests/test_bag.py::test_aggregation[1] dask/bag/tests/test_bag.py::test_aggregation[3] dask/bag/tests/test_bag.py::test_aggregation[4] dask/bag/tests/test_bag.py::test_non_splittable_reductions[1] dask/bag/tests/test_bag.py::test_non_splittable_reductions[10] dask/bag/tests/test_bag.py::test_std dask/bag/tests/test_bag.py::test_var dask/bag/tests/test_bag.py::test_join[identity] dask/bag/tests/test_bag.py::test_join[delayed] dask/bag/tests/test_bag.py::test_join[<lambda>] dask/bag/tests/test_bag.py::test_foldby dask/bag/tests/test_bag.py::test_foldby_tree_reduction dask/bag/tests/test_bag.py::test_map_partitions dask/bag/tests/test_bag.py::test_map_partitions_args_kwargs dask/bag/tests/test_bag.py::test_random_sample_size dask/bag/tests/test_bag.py::test_random_sample_prob_range dask/bag/tests/test_bag.py::test_random_sample_repeated_computation dask/bag/tests/test_bag.py::test_random_sample_different_definitions dask/bag/tests/test_bag.py::test_random_sample_random_state dask/bag/tests/test_bag.py::test_lazify_task dask/bag/tests/test_bag.py::test_lazify dask/bag/tests/test_bag.py::test_inline_singleton_lists dask/bag/tests/test_bag.py::test_take dask/bag/tests/test_bag.py::test_take_npartitions dask/bag/tests/test_bag.py::test_map_is_lazy dask/bag/tests/test_bag.py::test_can_use_dict_to_make_concrete dask/bag/tests/test_bag.py::test_read_text dask/bag/tests/test_bag.py::test_read_text_large dask/bag/tests/test_bag.py::test_read_text_encoding dask/bag/tests/test_bag.py::test_read_text_large_gzip dask/bag/tests/test_bag.py::test_from_sequence dask/bag/tests/test_bag.py::test_from_long_sequence dask/bag/tests/test_bag.py::test_product dask/bag/tests/test_bag.py::test_partition_collect dask/bag/tests/test_bag.py::test_groupby dask/bag/tests/test_bag.py::test_groupby_with_indexer dask/bag/tests/test_bag.py::test_groupby_with_npartitions_changed dask/bag/tests/test_bag.py::test_concat dask/bag/tests/test_bag.py::test_flatten dask/bag/tests/test_bag.py::test_concat_after_map dask/bag/tests/test_bag.py::test_args dask/bag/tests/test_bag.py::test_to_textfiles[gz-GzipFile] dask/bag/tests/test_bag.py::test_to_textfiles[-open] dask/bag/tests/test_bag.py::test_to_textfiles[bz2-BZ2File] dask/bag/tests/test_bag.py::test_to_textfiles_name_function_preserves_order dask/bag/tests/test_bag.py::test_to_textfiles_encoding dask/bag/tests/test_bag.py::test_to_textfiles_inputs dask/bag/tests/test_bag.py::test_to_textfiles_endlines dask/bag/tests/test_bag.py::test_string_namespace dask/bag/tests/test_bag.py::test_string_namespace_with_unicode dask/bag/tests/test_bag.py::test_str_empty_split dask/bag/tests/test_bag.py::test_map_with_iterator_function dask/bag/tests/test_bag.py::test_ensure_compute_output_is_concrete dask/bag/tests/test_bag.py::test_bag_class_extend dask/bag/tests/test_bag.py::test_gh715 dask/bag/tests/test_bag.py::test_bag_compute_forward_kwargs dask/bag/tests/test_bag.py::test_to_delayed dask/bag/tests/test_bag.py::test_to_delayed_optimize_graph dask/bag/tests/test_bag.py::test_from_delayed dask/bag/tests/test_bag.py::test_from_delayed_iterator dask/bag/tests/test_bag.py::test_range dask/bag/tests/test_bag.py::test_zip[1] dask/bag/tests/test_bag.py::test_zip[7] dask/bag/tests/test_bag.py::test_zip[10] dask/bag/tests/test_bag.py::test_zip[28] dask/bag/tests/test_bag.py::test_repartition[1-1] dask/bag/tests/test_bag.py::test_repartition[1-2] dask/bag/tests/test_bag.py::test_repartition[1-7] dask/bag/tests/test_bag.py::test_repartition[1-11] dask/bag/tests/test_bag.py::test_repartition[1-23] dask/bag/tests/test_bag.py::test_repartition[2-1] dask/bag/tests/test_bag.py::test_repartition[2-2] dask/bag/tests/test_bag.py::test_repartition[2-7] dask/bag/tests/test_bag.py::test_repartition[2-11] dask/bag/tests/test_bag.py::test_repartition[2-23] dask/bag/tests/test_bag.py::test_repartition[5-1] dask/bag/tests/test_bag.py::test_repartition[5-2] dask/bag/tests/test_bag.py::test_repartition[5-7] dask/bag/tests/test_bag.py::test_repartition[5-11] dask/bag/tests/test_bag.py::test_repartition[5-23] dask/bag/tests/test_bag.py::test_repartition[12-1] dask/bag/tests/test_bag.py::test_repartition[12-2] dask/bag/tests/test_bag.py::test_repartition[12-7] dask/bag/tests/test_bag.py::test_repartition[12-11] dask/bag/tests/test_bag.py::test_repartition[12-23] dask/bag/tests/test_bag.py::test_repartition[23-1] dask/bag/tests/test_bag.py::test_repartition[23-2] dask/bag/tests/test_bag.py::test_repartition[23-7] dask/bag/tests/test_bag.py::test_repartition[23-11] dask/bag/tests/test_bag.py::test_repartition[23-23] dask/bag/tests/test_bag.py::test_repartition_names dask/bag/tests/test_bag.py::test_accumulate dask/bag/tests/test_bag.py::test_groupby_tasks dask/bag/tests/test_bag.py::test_groupby_tasks_names dask/bag/tests/test_bag.py::test_groupby_tasks_2[1000-20-100] dask/bag/tests/test_bag.py::test_groupby_tasks_2[12345-234-1042] dask/bag/tests/test_bag.py::test_groupby_tasks_3 dask/bag/tests/test_bag.py::test_to_textfiles_empty_partitions dask/bag/tests/test_bag.py::test_reduction_empty dask/bag/tests/test_bag.py::test_reduction_empty_aggregate[1] dask/bag/tests/test_bag.py::test_reduction_empty_aggregate[2] dask/bag/tests/test_bag.py::test_reduction_empty_aggregate[4] dask/bag/tests/test_bag.py::test_reduction_with_non_comparable_objects dask/bag/tests/test_bag.py::test_empty dask/bag/tests/test_bag.py::test_bag_picklable dask/bag/tests/test_bag.py::test_msgpack_unicode dask/bag/tests/test_bag.py::test_bag_with_single_callable dask/bag/tests/test_bag.py::test_optimize_fuse_keys dask/bag/tests/test_bag.py::test_reductions_are_lazy dask/bag/tests/test_bag.py::test_repeated_groupby dask/bag/tests/test_bag.py::test_temporary_directory dask/bag/tests/test_bag.py::test_empty_bag dask/bag/tests/test_bag.py::test_bag_paths
dask/bag/core.py
dask/bag/core.py