[Bug]: SyncClientMixin.low() masks all early failures with UnboundLocalError: proc_fn
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 15.7k
- Forks
- 5.6k
- Avg merge
- 2d 44m
- Merged PRs (30d)
- 80
Description
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 jammy
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in salt/client/mixins.py, especially SyncClientMixin.low() around lines 338-421, and reproduce the plain RunnerClient example with a nonexistent function. Trace the early-failure and cleanup paths, then verify that the original CommandExecutionError is returned in data["return"] rather than being replaced by UnboundLocalError, with job and event handling still reached.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100