What's happening?
Windows: a running user-installed LibreHardwareMonitor is not always reused, so TRCC launches its bundled v0.9.6 alongside it
Version: 9.9.12 (Windows, PyInstaller build)
Area: src/trcc/adapters/sensors/_lhm.py, .github/workflows/windows.yml
Summary
TRCC bundles LibreHardwareMonitor v0.9.6 and launches it as a hidden subprocess to read sensors from the root\LibreHardwareMonitor WMI namespace. It is supposed to reuse an already-running LHM instead, and it does when the namespace is readable. But the reuse gate tests only the WMI namespace, never the process. If the user already has LibreHardwareMonitor running and that namespace is not queryable by us at that moment, TRCC silently starts its own older bundled copy on top of the user's instance.
Expected
If LibreHardwareMonitor.exe is already running on the machine, TRCC uses it and never launches the bundled copy, whatever state that instance's WMI publishing is in.
Actual
A second LibreHardwareMonitor.exe (ours, pinned at v0.9.6) starts hidden and detached. The user gets two instances competing for the same ring-0 monitoring driver and the same WMI namespace, and the one actually feeding TRCC may be older than the one they installed and maintain themselves.
Root cause
LhmSubprocess.start() (src/trcc/adapters/sensors/_lhm.py:203) resolves reuse like this:
existing = self._probe() at :216, which is _probe_wmi_namespace() at :62. That function imports wmi, opens root\LibreHardwareMonitor, and calls list(ns.Hardware()). Every failure path, missing wmi package, COM error, absent namespace, or a raise from the enumeration, is swallowed into a single log.debug and returns None. It is one attempt, with no retry.
- On a hit,
:218 logs LibreHardwareMonitor already running; reusing WMI namespace and we are done. This part works.
- On
None, control falls through to self._spawn() at :235, which is _spawn_lhm() at :99. That launches the bundled exe from <exe-dir>/lhm/ with CREATE_NO_WINDOW and DETACHED_PROCESS, so nothing visible tells the user a second instance just started.
There is no process-level check anywhere in the module. _LHM_PROCESS_NAME = "LibreHardwareMonitor.exe" exists at :37, but it is used only to build candidate paths for the bundled exe at :90-91. So "is LHM running?" is answered exclusively by "can I read its WMI namespace right now?", and those two questions are not the same question.
Worth noting what is already correct, so the fix stays small: stop() at :255 terminates only a process we spawned ourselves, so a reused user instance is correctly left running. And the #191 guard at :229 stops us spawning a second copy of our own. Neither guard helps here, because the gate never learns the user's instance exists.
Suspected trigger conditions (not yet confirmed)
The code-level mechanism above is confirmed by reading. What makes the namespace unreadable while LHM runs still needs confirming on a real Windows box:
- The user's LHM is running without administrator rights, so it never registers the namespace.
- The user's own LHM configuration has WMI publishing disabled. Note we pre-seed our own
LibreHardwareMonitor.config for the bundled copy (.github/workflows/windows.yml:117), but the user's install has their config, not ours.
- TRCC is not elevated and cannot read the namespace the user's elevated instance publishes.
- Plain timing: the user's LHM is still starting up, and our probe is a single attempt with no retry.
Impact
- Two monitoring apps contend for the same kernel driver, which is the classic cause of stalled or zeroed sensor readings.
- Sensors are served by our pinned v0.9.6 rather than whatever the user installed, which can be newer. This matters more than usual given the WinRing0 to PawnIO transition documented in
doc/SENSOR_RESEARCH.md.
- It is invisible. The spawn is hidden and detached, so the only evidence is Task Manager or the log.
How to confirm on a Windows machine
- Start LibreHardwareMonitor yourself and leave it running.
- Start TRCC.
- Check Task Manager for two
LibreHardwareMonitor.exe processes.
- Check
%USERPROFILE%\.trcc\trcc.log for which branch fired. Spawned LibreHardwareMonitor (pid=...) is the bug. LibreHardwareMonitor already running; reusing WMI namespace is the correct path. Both are INFO and the log file keeps DEBUG at every verbosity, so no special flag is needed, and trcc report will carry it.
Repeat with LHM started elevated and non-elevated, since that is the likeliest split between the working and broken path.
Proposed fix
Make the reuse gate ask about the process, not just the namespace. Before spawning, check for a live LibreHardwareMonitor.exe by process name using psutil, which is already a hard dependency. If one is found:
- do not spawn anything, ever, regardless of namespace state;
- log the decision with the resolved pid and the reason;
- if the namespace is not readable yet, wait on the existing instance the same way we already wait on one we spawned, then degrade to "LHM present but not publishing to WMI" with an actionable warning rather than starting a competing copy.
This stays inside the adapter layer where OS-native process detection belongs, and it leaves stop() semantics untouched: we still only terminate what we started.
Separately worth considering, but not part of this fix: if the user's instance is present and publishing, prefer it explicitly and log the version we are reading from, so a sensor bug report tells us which LHM produced the numbers.
Diagnostic report (trcc report)
Device (model + USB ID if you know it)
No response
Steps to reproduce (optional)
No response
Before submitting
What's happening?
Windows: a running user-installed LibreHardwareMonitor is not always reused, so TRCC launches its bundled v0.9.6 alongside it
Version: 9.9.12 (Windows, PyInstaller build)
Area:
src/trcc/adapters/sensors/_lhm.py,.github/workflows/windows.ymlSummary
TRCC bundles LibreHardwareMonitor v0.9.6 and launches it as a hidden subprocess to read sensors from the
root\LibreHardwareMonitorWMI namespace. It is supposed to reuse an already-running LHM instead, and it does when the namespace is readable. But the reuse gate tests only the WMI namespace, never the process. If the user already has LibreHardwareMonitor running and that namespace is not queryable by us at that moment, TRCC silently starts its own older bundled copy on top of the user's instance.Expected
If
LibreHardwareMonitor.exeis already running on the machine, TRCC uses it and never launches the bundled copy, whatever state that instance's WMI publishing is in.Actual
A second
LibreHardwareMonitor.exe(ours, pinned at v0.9.6) starts hidden and detached. The user gets two instances competing for the same ring-0 monitoring driver and the same WMI namespace, and the one actually feeding TRCC may be older than the one they installed and maintain themselves.Root cause
LhmSubprocess.start()(src/trcc/adapters/sensors/_lhm.py:203) resolves reuse like this:existing = self._probe()at:216, which is_probe_wmi_namespace()at:62. That function importswmi, opensroot\LibreHardwareMonitor, and callslist(ns.Hardware()). Every failure path, missingwmipackage, COM error, absent namespace, or a raise from the enumeration, is swallowed into a singlelog.debugand returnsNone. It is one attempt, with no retry.:218logsLibreHardwareMonitor already running; reusing WMI namespaceand we are done. This part works.None, control falls through toself._spawn()at:235, which is_spawn_lhm()at:99. That launches the bundled exe from<exe-dir>/lhm/withCREATE_NO_WINDOWandDETACHED_PROCESS, so nothing visible tells the user a second instance just started.There is no process-level check anywhere in the module.
_LHM_PROCESS_NAME = "LibreHardwareMonitor.exe"exists at:37, but it is used only to build candidate paths for the bundled exe at:90-91. So "is LHM running?" is answered exclusively by "can I read its WMI namespace right now?", and those two questions are not the same question.Worth noting what is already correct, so the fix stays small:
stop()at:255terminates only a process we spawned ourselves, so a reused user instance is correctly left running. And the#191guard at:229stops us spawning a second copy of our own. Neither guard helps here, because the gate never learns the user's instance exists.Suspected trigger conditions (not yet confirmed)
The code-level mechanism above is confirmed by reading. What makes the namespace unreadable while LHM runs still needs confirming on a real Windows box:
LibreHardwareMonitor.configfor the bundled copy (.github/workflows/windows.yml:117), but the user's install has their config, not ours.Impact
doc/SENSOR_RESEARCH.md.How to confirm on a Windows machine
LibreHardwareMonitor.exeprocesses.%USERPROFILE%\.trcc\trcc.logfor which branch fired.Spawned LibreHardwareMonitor (pid=...)is the bug.LibreHardwareMonitor already running; reusing WMI namespaceis the correct path. Both are INFO and the log file keeps DEBUG at every verbosity, so no special flag is needed, andtrcc reportwill carry it.Repeat with LHM started elevated and non-elevated, since that is the likeliest split between the working and broken path.
Proposed fix
Make the reuse gate ask about the process, not just the namespace. Before spawning, check for a live
LibreHardwareMonitor.exeby process name usingpsutil, which is already a hard dependency. If one is found:This stays inside the adapter layer where OS-native process detection belongs, and it leaves
stop()semantics untouched: we still only terminate what we started.Separately worth considering, but not part of this fix: if the user's instance is present and publishing, prefer it explicitly and log the version we are reading from, so a sensor bug report tells us which LHM produced the numbers.
Diagnostic report (
trcc report)Device (model + USB ID if you know it)
No response
Steps to reproduce (optional)
No response
Before submitting
pip install --upgrade trcc-linux, or the latest installer)trcc reportabove — or my install/launch error if it won't run