From c85f9a6dfcf2bb51902cc27504dce63d0fd63a39 Mon Sep 17 00:00:00 2001 From: Joaquin Hui Gomez <132194176+joaquinhuigomez@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:27:47 +0000 Subject: [PATCH 1/2] fix: guard against non-iterable target in list_jobs search_target When search_target is used in list_jobs, the code assumed Target would always be a string or list. Execution/state failures can leave Target as a non-iterable value (e.g. int), causing a TypeError. Skip non-iterable targets instead of crashing. Closes #68780 --- salt/runners/jobs.py | 2 ++ tests/pytests/unit/runners/test_jobs.py | 42 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index f1ff0f8b4921..2f530d6b20c7 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -334,6 +334,8 @@ def list_jobs( targets = ret[item]["Target"] if isinstance(targets, str): targets = [targets] + elif not isinstance(targets, (list, tuple)): + targets = [] for target in targets: for key in salt.utils.args.split_input(search_target): if fnmatch.fnmatch(target, key): diff --git a/tests/pytests/unit/runners/test_jobs.py b/tests/pytests/unit/runners/test_jobs.py index 8d9fe3853a77..0edb67304743 100644 --- a/tests/pytests/unit/runners/test_jobs.py +++ b/tests/pytests/unit/runners/test_jobs.py @@ -70,3 +70,45 @@ def __init__(self, *args, **kwargs): assert jobs.list_jobs(search_target="node-1-2.com") == returns["node-1-2.com"] assert jobs.list_jobs(search_target="non-existant") == returns["non-existant"] + + +def test_list_jobs_with_non_iterable_target(): + """ + test jobs.list_jobs does not crash when Target is not iterable (e.g. int) + + Regression test for https://github.com/saltstack/salt/issues/68780 + """ + mock_jobs_cache = { + "20160524035503086853": { + "Arguments": [], + "Function": "test.ping", + "StartTime": "2016, May 24 03:55:03.086853", + "Target": 3, + "Target-type": "glob", + "User": "root", + }, + "20160524035524895387": { + "Arguments": [], + "Function": "test.ping", + "StartTime": "2016, May 24 03:55:24.895387", + "Target": "node-1-1.com", + "Target-type": "glob", + "User": "sudo_ubuntu", + }, + } + + def return_mock_jobs(): + return mock_jobs_cache + + class MockMasterMinion: + + returners = {"local_cache.get_jids": return_mock_jobs} + + def __init__(self, *args, **kwargs): + pass + + with patch.object(salt.minion, "MasterMinion", MockMasterMinion): + # Should not raise TypeError; the non-iterable target job is skipped + result = jobs.list_jobs(search_target="node-1-1.com") + assert "20160524035524895387" in result + assert "20160524035503086853" not in result From 010ba2a424d2302466462de6ec6914181d6fb49f Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Sun, 7 Jun 2026 16:43:43 -0700 Subject: [PATCH 2/2] Log warning on non-iterable Target skip + add changelog for #68780 --- changelog/68780.fixed.md | 1 + salt/runners/jobs.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 changelog/68780.fixed.md diff --git a/changelog/68780.fixed.md b/changelog/68780.fixed.md new file mode 100644 index 000000000000..379a45b3f943 --- /dev/null +++ b/changelog/68780.fixed.md @@ -0,0 +1 @@ +Guard against non-iterable `Target` in cached job entries when filtering by `search_target` in `salt-run jobs.list_jobs`. diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index 2f530d6b20c7..f163eb55a43d 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -335,6 +335,9 @@ def list_jobs( if isinstance(targets, str): targets = [targets] elif not isinstance(targets, (list, tuple)): + log.warning( + "Job %s has non-iterable Target %r; skipping", item, targets + ) targets = [] for target in targets: for key in salt.utils.args.split_input(search_target):