How MTPLX Handles Fan Control via ThermalForge and Crash Recovery
TLDR: MTPLX delegates fan control to the open-source ThermalForge utility, using a JSON marker-file ownership system and a detached side-car watchdog to guarantee that fans return to automatic curves even if the main process crashes or receives SIGKILL.
MTPLX is an open-source macOS power management toolkit that leverages ThermalForge for hardware-level fan control. Unlike simple shell scripts, the implementation in [mtplx/thermal.py](https://github.com/youssofal/MTPLX/blob/main/mtplx/thermal.py) provides verified command execution, state persistence, and robust crash recovery mechanisms. This article examines the architectural layers that enable safe fan control via ThermalForge while preventing the system from being left with permanently maxed-out fans after unexpected terminations.
Detecting and Selecting the ThermalForge Binary
The detection logic begins with detect_thermal_control(), which orchestrates tool discovery. The helper _find_thermalforge_ prioritizes a private copy installed in ~/.mtplx/bin over system-wide installations, falling back to any thermalforge binary found on PATH. If no binary is located, the function returns INSTALL_INSTRUCTIONS to guide the user through setup rather than failing silently.
Executing and Verifying Fan Commands
Setting Thermal Profiles
When a user invokes mtplx max, the system calls set_thermal_profile_verified(). This function constructs command candidates via _profile_command_candidates and delegates execution to set_thermal_profile(). It issues password-less sudo commands to ThermalForge, requesting specific profiles such as "max" or "silent".
Status Verification
After issuing a command, MTPLX polls the daemon status using thermal_status() and _status_command_candidates. The verification layer parses JSON output through fan_summary() to confirm the hardware actually responded. Two boolean checks enforce correctness: _summary_indicates_max confirms the daemon registered the target RPM, while _summary_indicates_actual_ramp verifies the physical fans have spun up to the requested speed.
Crash Recovery Through Marker Files
To prevent "stuck" fan states, MTPLX implements an ownership marker system. When enabling max fan mode, _write_max_marker serializes the process PID, ownership token, and binary path to ~/.mtplx/max-active.json, protected by _max_marker_lock to serialize concurrent access. An atexit handler installed by install_max_lifecycle_hooks() triggers restore_thermal_profile_verified() on graceful exit, issuing thermalforge auto and confirming the return to automatic curves.
If the process crashes or is killed with SIGKILL, the marker persists. On the next launch, check_and_recover_stale_max() detects the stale PID, invokes restore_thermal_profile_verified(), and clears the marker. This guarantees the system never remains in an unintended thermal state due to a previous crash.
The Side-Car Watchdog for Hard Crashes
Because atexit handlers do not survive SIGHUP or terminal closure, MTPLX spawns a detached side-car via _spawn_thermal_sidecar() defined in [mtplx/thermal_sidecar.py](https://github.com/youssofal/MTPLX/blob/main/mtplx/thermal_sidecar.py). Running in its own session, this watchdog monitors the parent PID and executes thermalforge auto through password-less sudo (configured via install_passwordless_sudoers_rule()) upon detecting parent death. This mechanism provides a safety net when the main process cannot execute cleanup code.
Practical Implementation Examples
# Example: Enable max-fan mode and verify it worked
result = set_thermal_profile_verified(
profile="max", # "max" or "silent"
settle_seconds=1.0, # give daemon time to update the target RPM
require_actual_ramp=True, # wait for the hardware to spin up
)
print(result["message"]) # Human-readable outcome
# → "fans ramped to max (actual 7800 RPM; target 7900 RPM)"
# Example: Restore default fan behaviour (used on graceful shutdown)
restore = restore_thermal_profile_verified()
print(restore["message"]) # "fan profile restored" if successful
# Example: Automatic crash recovery on next launch
recovery = check_and_recover_stale_max()
if recovery["recovered"]:
print("Recovered from previous crash – fans are now auto.")
# Example: Install password-less sudo rule for ThermalForge (once per user)
install = install_passwordless_sudoers_rule()
print(install["message"])
# → "Passwordless sudo for thermalforge is configured."
Summary
- MTPLX detects ThermalForge binaries using
detect_thermal_control(), preferring local copies in~/.mtplx/binover system paths. - Verified execution ensures fan commands actually affect hardware through
set_thermal_profile_verified()and dual-layer status checks. - A JSON marker file tracks active "max" mode sessions with file-locking to prevent race conditions.
- Automatic recovery via
check_and_recover_stale_max()restores default fan curves if a previous session crashed. - The side-car watchdog survives terminal closure and SIGKILL scenarios to guarantee
thermalforge autoexecution via [mtplx/thermal_sidecar.py](https://github.com/youssofal/MTPLX/blob/main/mtplx/thermal_sidecar.py).
Frequently Asked Questions
What happens if MTPLX crashes while fans are set to maximum speed?
The ownership marker at ~/.mtplx/max-active.json remains on disk. On the next start, check_and_recover_stale_max() detects the stale PID and automatically runs restore_thermal_profile_verified() to return fans to auto mode before clearing the marker.
Why does MTPLX use a side-car process for fan control?
The side-car, implemented in [mtplx/thermal_sidecar.py](https://github.com/youssofal/MTPLX/blob/main/mtplx/thermal_sidecar.py), survives terminal closure and SIGKILL signals that would otherwise prevent cleanup code from running. It ensures fans never stay stuck at maximum speed when the parent process dies unexpectedly.
How does MTPLX verify that ThermalForge actually changed the fan speeds?
After issuing commands, MTPLX polls daemon status via thermal_status() and checks both _summary_indicates_max for target RPM confirmation and _summary_indicates_actual_ramp for physical fan rotation verification.
Where does MTPLX store the crash recovery marker?
The marker is stored at ~/.mtplx/max-active.json and contains the process PID, ownership token, and ThermalForge binary path, protected by a file lock at _max_marker_lock to serialize concurrent access.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →