[Bug]: SyncClientMixin.low() masks all early failures with UnboundLocalError: proc_fn
What happened?
In salt/client/mixins.py::SyncClientMixin.low(), proc_fn is assigned at line 381, but the finally block at line 415 unconditionally references it at line 418:
Anything raising between lines 339 and 380 is correctly captured by the broad except at 404 — and then discarded, because the finally raises UnboundLocalError on its way out. Affected call sites include verify_fun() (line 340), the func_globals injection loop, salt.utils.args.format_call(), and jid_event.fire_event(data, "new").
Two consequences:
The real exception is never logged or returned. Callers see only UnboundLocalError: cannot access local variable 'proc_fn' where it is not associated with a value, which points at cleanup code unrelated to the actual fault. Because the finally raises, execution never reaches the store_job() call or namespaced_event.fire_event(data, "ret") that follow it, so the failure is absent from the job cache and the event bus too. Note that a permissions failure on the proc file itself does not trigger this — proc_fn is already bound by the time fopen() runs at line 382. The UnboundLocalError is always a symptom of an earlier, hidden fault, which makes it actively misleading to diagnose.
Any master. Reproducible with a plain RunnerClient; no special configuration required.
Steps to reproduce
import salt.config import salt.runner
opts = salt.config.master_config("/etc/salt/master") client = salt.runner.RunnerClient(opts) client.low("nonexistent.function", {"fun": "nonexistent.function"})
The control flow in isolation:
import os
def low(fail_early): try: if fail_early: raise RuntimeError("the real error") proc_fn = os.path.join("/tmp", "proc", "jid") open(proc_fn, "w+b") except (Exception, SystemExit) as ex: print("broad except captured:", ex) # real error captured here... finally: try: os.remove(proc_fn) # ...then destroyed here except OSError: pass
low(True)
broad except captured: the real error
UnboundLocalError: local variable 'proc_fn' referenced before assignment
Expected behavior
verify_fun() raises CommandExecutionError: 'nonexistent.function' is not available., and low() returns it in data["return"] as the broad except intends.
Actual behavior
UnboundLocalError: cannot access local variable 'proc_fn' where it is not associated with a value
Suggested fix
try:
proc_fn = None self_functions = copy.copy(self.functions) salt.utils.lazy.verify_fun(self_functions, fun)
@@ finally: # Job has finished or issue found, so let's clean up after ourselves
try:os.remove(proc_fn)except OSError as err:log.debug("Error attempting to remove master job tracker: %s", err)
if proc_fn is not None:try:os.remove(proc_fn)except OSError as err:log.debug("Error attempting to remove master job tracker: %s", err)
Type of salt install
Official pkg
Major version
3008.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
ubuntu-22.04
salt --versions-report output
Salt Version:
Salt: 3008.2
Python Version:
Python: 3.14.6 (main, Jun 11 2026, 02:19:05) [GCC 11.2.0]
Dependency Versions:
cffi: 2.0.0
cherrypy: 18.10.0
cryptography: 48.0.0
dateutil: 2.9.0.post0
docker-py: Not Installed
gitdb: 4.0.12
gitpython: 3.1.50
Jinja2: 3.1.6
libgit2: Not Installed
looseversion: 1.3.0
M2Crypto: Not Installed
Mako: Not Installed
msgpack: 1.1.2
msgpack-pure: Not Installed
mysql-python: Not Installed
packaging: 24.0
pycparser: 3.00
pycrypto: 3.23.0
pycryptodome: 3.23.0
pygit2: Not Installed
python-gnupg: 0.5.6
PyYAML: 6.0.3
PyZMQ: 27.1.0
relenv: 0.22.14
smmap: 5.0.2
timelib: 0.3.0
Tornado: 6.5.7
ZMQ: 4.3.5
Salt Extensions:
saltext.azurerm: 4.4.1
Salt Package Information:
Package Type: onedir
System Versions:
dist: ubuntu 22.04.5 jammy
locale: utf-8
machine: x86_64
release: 6.8.0-1053-aws
system: Linux
version: Ubuntu 22.04.5 jammySource: saltstack/salt