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
-
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
-
-
-
-
log.debug("Error attempting to remove master job tracker: %s", 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 jammy
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
@@
finally:
# Job has finished or issue found, so let's clean up after ourselves
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 jammy