Skip to content

runners/jobs: don't iterate non-iterable Target values in list_jobs#68969

Closed
SAY-5 wants to merge 1 commit into
saltstack:3007.xfrom
SAY-5:fix/list-jobs-noniterable-target-68780
Closed

runners/jobs: don't iterate non-iterable Target values in list_jobs#68969
SAY-5 wants to merge 1 commit into
saltstack:3007.xfrom
SAY-5:fix/list-jobs-noniterable-target-68780

Conversation

@SAY-5

@SAY-5 SAY-5 commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Hardens salt.runners.jobs.list_jobs against non-iterable Target values in the job cache.

list_jobs's search_target filter reads Target for each cached job, wraps strings in a list, then iterates:

targets = ret[item]["Target"]
if isinstance(targets, str):
    targets = [targets]
for target in targets:
    ...

If Target is neither a string nor an iterable — which happens for failed or aborted jobs (#68780) — for target in targets: raises TypeError and kills the whole query, not just the one bad entry.

This PR adds a small elif not isinstance(targets, (list, tuple, set)): targets = [] guard so a malformed Target is treated the same way a missing Target key already is: no target to match, skip and move on. String/list/tuple/set behaviour is unchanged.

What issues does this PR fix or reference?

Fixes #68780

Previous Behavior

Calling jobs.list_jobs search_target=... against a job cache that contains any entry with a non-iterable Target (e.g. Target: None from a failed job) raises:

TypeError: argument of type 'NoneType' is not iterable

and list_jobs returns no result at all.

New Behavior

Entries with a non-iterable Target are silently skipped from search_target matching; the rest of the query returns as expected. String/list/tuple/set targets keep working exactly as before.

Merge requirements satisfied?

  • Changelog: changelog/68780.fixed.md
  • Tests — the matching code path is inside a large list_jobs loop; happy to add a targeted unit test that seeds the cache with Target: None and asserts list_jobs returns rather than raises, if the maintainers prefer before merge.

Commits signed with GPG?

DCO-signed off (Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>).

@SAY-5 SAY-5 requested a review from a team as a code owner April 20, 2026 18:39
Comment thread salt/runners/jobs.py Outdated
targets = ret[item]["Target"]
if isinstance(targets, str):
targets = [targets]
elif not isinstance(targets, (list, tuple, set)):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just check if the type is Iterable instead of trying to enumerate possible iterable types.

from collections.abc import Iterable

isinstance(obj, Iterable)

I suggest not silently masking errors though and instead log a warning or at least a debug/trace message.

Failed or aborted jobs can leave Target as None or some other
non-iterable value in the job cache. list_jobs used to iterate it
unconditionally, raising TypeError and aborting the whole listing,
which made it impossible to even see the malformed entries in order
to fix them.

Replace the explicit (list, tuple, set) instance check with a single
collections.abc.Iterable check per @bdrx312's review feedback - that
covers any future iterable container the cache may grow without
re-listing every type. Strings are pulled out beforehand so a single
target string still gets wrapped in a list rather than being treated
as a sequence of characters.

When a non-iterable Target is hit, log it at DEBUG with the offending
job id so operators have a breadcrumb back to the malformed cache row,
rather than the previous silent skip.

Add a regression test that mixes a normal job entry with one carrying
Target=None, asserts the normal one is still matched, the bad one is
filtered out, and the debug log carries the bad jid. The test fails on
master with a missing log entry (because the old code silently
skipped), confirming both behaviour changes.

Closes saltstack#68780

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
@SAY-5 SAY-5 force-pushed the fix/list-jobs-noniterable-target-68780 branch from ed80d4a to ed84164 Compare April 21, 2026 07:17
@SAY-5

SAY-5 commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @bdrx312 — applied both suggestions:

  1. Iterable check: replaced the explicit (list, tuple, set) instance check with isinstance(targets, collections.abc.Iterable), so any future iterable container the cache may grow (deque, generator, custom class) just works without re-listing every type. Strings are still pulled out beforehand so a single target string keeps getting wrapped in a list rather than being treated as a sequence of characters.
  2. Non-silent skip: when a non-iterable Target is hit, log it at DEBUG with the offending job id, so operators have a breadcrumb back to the malformed cache row instead of having it disappear.

Verified locally on macOS, Python 3.13:

  • black --check and isort --check: clean
  • pytest tests/pytests/unit/runners/test_jobs.py -v: both tests pass
  • Reverted salt/runners/jobs.py to master and re-ran the new test to confirm it fails with AssertionError: assert '20260421000000000002' in '' — i.e. the assertion pins the new debug-log behaviour as a regression test

Net diff: jobs.py +5 / -2; new test test_list_jobs_with_non_iterable_target covers both the no-crash and the debug-log paths.

@bdrx312

bdrx312 commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Also there is another PR also attempting to fix this #68862

@SAY-5

SAY-5 commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #68862 — thanks @bdrx312.

@SAY-5 SAY-5 closed this May 5, 2026
@bdrx312

bdrx312 commented May 6, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of #68862 — thanks @bdrx312.

The author of that PR seems to be non-responsive and your solution is better than the current form in the other PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants