Skip to content

Commit 267e8c0

Browse files
committed
docs: catch up on the power-metric standardization
The docs still said Apple Pro/Max and Windows had no CPU power — both wrong now. Pro/Max reads CPU power from raw SMC cluster keys, Windows reads it from a user-run Scaphandre. Fixed the stale "hardware limit" blockquotes, the metrics table, deployment/troubleshooting rows, and the guide index. Also wrote the missing ADR (0021) for the whole standardization arc — the cpu/gpu/total trio, per-platform source layering, source-aware total, in-process-first helper — with a mermaid flow of how power.cpu picks its source per OS. Marked the "power on Windows is a permanent non-goal" bit of ADR 0015 superseded.
1 parent d0fd98b commit 267e8c0

10 files changed

Lines changed: 208 additions & 25 deletions

docs/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ Start here, then jump to the guide for what you want to do.
2323
| [Monitor a Fleet](guides/02-monitor-a-fleet.md) | watch many hosts from one station |
2424
| [Secure Deployment](guides/03-secure-deployment.md) | require TLS + an enrollment token |
2525
| [Privileged Metrics](guides/04-privileged-metrics.md) | unlock power, GPU, and full thermal (overview + deployment) |
26-
|[macOS / Apple Silicon](guides/13-privileged-macos.md) | IOReport GPU/power, SMC whole-system power, ANE |
27-
|[Linux + NVIDIA](guides/14-privileged-linux-nvidia.md) | GPU via `nvidia-smi`, CPU power via the helper |
26+
|[macOS / Apple Silicon](guides/13-privileged-macos.md) | IOReport GPU/power, SMC CPU + whole-system power, ANE |
27+
|[Linux + NVIDIA](guides/14-privileged-linux-nvidia.md) | GPU via `nvidia-smi`, CPU power via RAPL (helper) |
2828
|[Linux + AMD](guides/15-privileged-linux-amd.md) | Radeon / Strix Halo via `amd-smi` or amdgpu sysfs |
29-
|[Windows](guides/16-privileged-windows.md) | WMI thermal, `nvidia-smi` GPU; no RAPL power |
29+
|[Windows](guides/16-privileged-windows.md) | WMI thermal, `nvidia-smi` GPU, CPU power via Scaphandre |
3030
| [Federation (Bifröst)](guides/05-federation.md) | span multiple sites / networks |
3131
| [Process View & Commands](guides/06-control-plane.md) | see a host's process table (top) and run allow-listed diagnostics |
3232
| [Log Streaming](guides/07-log-streaming.md) | tail host logs in the dashboard |

docs/architecture/0015-windows-privileged-metrics.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ deciders:
1111

1212
# 0015 — Windows privileged metrics
1313

14+
> **Update (2026-07-02, see [ADR-0021](0021-power-metric-standardization-and-source-layering.md)):**
15+
> the "CPU package power is a permanent non-goal on Windows" conclusion below is
16+
> **superseded**. Heimdall does not ship a driver, but it now reads CPU power from
17+
> a user-run **[Scaphandre](https://github.com/hubblo-org/scaphandre)** (which
18+
> installs the signed Hubblo RAPL driver) over its Prometheus endpoint, reported as
19+
> `power.cpu`. The WMI temperature path in this ADR still stands.
20+
1421
## 1. Context
1522

1623
The privileged helper (`app/internal/helper`) gives Heimdall the metrics the
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
adr: "0021"
3+
title: "Power metric standardization (cpu/gpu/total) and per-platform source layering"
4+
status: accepted
5+
date: "2026-07-02"
6+
supersedes: null
7+
superseded_by: null
8+
deciders:
9+
- "Kinn Coelho Juliao"
10+
---
11+
12+
# 0021 — Power metric standardization (cpu/gpu/total) and per-platform source layering
13+
14+
## 1. Context
15+
16+
Power reporting had grown platform-specific and inconsistent, and fleet
17+
observation surfaced concrete wrongness:
18+
19+
- `power.pkg` meant **"CPU package"** on Linux (RAPL) but **"whole system"** on
20+
macOS (SMC `PSTR`), so the same key was not comparable across hosts.
21+
- On Linux `power.cpu` was the RAPL *core* subdomain while `power.pkg` was the
22+
package — two CPU-ish numbers that didn't reconcile.
23+
- **AMD APU (Strix Halo)** under-reported: only the amdgpu rail showed, so a box
24+
that btop measured at ~139 W read ~52 W.
25+
- A busy discrete GPU (RTX PRO 6000 at ~500 W) was hidden behind a ~30 W CPU
26+
package figure, because the panel headlined `power.pkg`.
27+
- **Apple Pro/Max** appeared to have no CPU power at all — IOReport and
28+
`powermetrics` both report `CPU Power: 0 mW` there.
29+
- **Windows** had no CPU power path.
30+
31+
The metric-adapter contract (ADR-0003) and the optional privileged helper
32+
(ADR-0004) still hold; this decision layers on top of them.
33+
34+
## 2. Goals / Non-Goals
35+
36+
**Goals:**
37+
- One comparable power model on every OS/chip: `power.cpu`, `power.gpu`,
38+
`power.total`.
39+
- Read the *best available* source per platform; never fabricate a value.
40+
- Explain every missing reading (Unavailable-with-reason), never a silent blank.
41+
- Backward compatible across a mixed-version fleet.
42+
43+
**Non-Goals:**
44+
- Shipping our own kernel driver (Windows RAPL) — we read a user-installed one.
45+
- A full per-chip SMC key map for every Apple SoC (we map what we can verify).
46+
- Per-core wattage (no vendor exposes it; see §5).
47+
48+
## 3. Proposal
49+
50+
**Standard trio, everywhere:**
51+
- `power.cpu` — CPU power (the whole CPU complex).
52+
- `power.gpu` — GPU power.
53+
- `power.total` — whole machine. Retire `power.pkg`.
54+
55+
**Per-platform source, layered (first that yields a value wins):**
56+
57+
| Platform | `power.cpu` | `power.total` |
58+
|---|---|---|
59+
| Linux (Intel/AMD) | RAPL package (root, via helper) | `cpu + gpu (+ npu)` |
60+
| Linux (ARM/GB10) | Unavailable — no RAPL | `gpu` |
61+
| Windows | Scaphandre scrape (its Hubblo RAPL driver) | `cpu + gpu` |
62+
| macOS **base** (M1–M4) | IOReport per-domain CPU | SMC `PSTR` |
63+
| macOS **Pro/Max/Ultra** | raw SMC cluster keys (`PC02+PC03+PC42+PC43` on M3 Max) | SMC `PSTR` |
64+
65+
`power.total` is a source-aware sum: on Apple it is the SMC whole-system rail
66+
directly (never `cpu+gpu`, which would double-count); everywhere else the GPU is
67+
a physically separate rail, so it is `cpu + gpu (+ npu)`.
68+
69+
**`power.cpu` source selection (first that yields a value wins):**
70+
71+
```mermaid
72+
flowchart TD
73+
start([collect power.cpu]) --> os{OS?}
74+
75+
os -->|Linux| rapl{RAPL readable?<br/>root / helper}
76+
rapl -->|yes| lcpu[power.cpu = RAPL package]
77+
rapl -->|no ARM/GB10| lnone[Unavailable: no RAPL]
78+
79+
os -->|Windows| scaph{Scaphandre<br/>endpoint up?}
80+
scaph -->|yes| wcpu[power.cpu = Scaphandre scrape]
81+
scaph -->|no| wnone[Unavailable: run Scaphandre]
82+
83+
os -->|macOS| io{IOReport CPU<br/>reads nonzero?<br/>base dies}
84+
io -->|yes| mcpu[power.cpu = IOReport CPU]
85+
io -->|no, Pro/Max/Ultra| smc{SMC cluster keys<br/>present and in bound?}
86+
smc -->|yes| scpu["power.cpu = sum of SMC cluster keys<br/>'CPU complex SMC'"]
87+
smc -->|no / unmapped die| snone[Unavailable: no per-domain CPU power]
88+
```
89+
90+
The macOS path is the novel one: base dies report CPU power through IOReport, but
91+
Pro/Max/Ultra report `0` there, so Heimdall falls to the raw SMC cluster keys with
92+
a sanity bound (below) before giving up.
93+
94+
**Apple SMC per-domain CPU** (the key novelty): the IOReport energy model reports
95+
0 for CPU on Pro/Max, but the raw AppleSMC per-cluster power keys still carry it
96+
(the same source Stats reads). `smcReadFloat(key)` reads any AppleSMC `flt ` key;
97+
`smcCPUPower()` sums the CPU cluster keys, **gated by a sanity bound** (reject
98+
NaN/Inf/negative/`>200 W`) so a chip whose keys mean something else falls back to
99+
Unavailable rather than showing garbage.
100+
101+
**Helper is in-process-first:** the daemon reads privileged metrics itself first;
102+
it only consults the helper when it *can't* read a CPU power rail (an unprivileged
103+
Linux daemon needing RAPL). On macOS the daemon has power via SMC/IOReport, so it
104+
never calls the (slow, `powermetrics`-bound) helper — running the helper on Apple
105+
Silicon is harmless.
106+
107+
## 4. Alternatives Considered
108+
109+
| Option | Pros | Cons | Why Rejected |
110+
|---|---|---|---|
111+
| Keep platform-specific names (`power.pkg` etc.) | no churn | not comparable across fleet; APU under-count persists | the whole point is comparability |
112+
| Ship our own Windows RAPL driver | works standalone | kernel-signing, liability, maintenance | read Scaphandre's signed driver instead |
113+
| Full per-chip Apple SMC key map (Stats-style) | accurate on every chip | large, ongoing reverse-engineering | verify what we have; safe fallback elsewhere |
114+
| Helper-first (prior behaviour) | one source of truth | a slow helper (macOS `powermetrics`) blanks *all* privileged metrics | in-process-first + bounded helper |
115+
| Naive `power.total = pkg + gpu` on all | simple | double-counts Apple SMC total and AMD-APU integrated GPU | source-aware sum |
116+
117+
## 5. Trade-offs and Risks
118+
119+
- **Reverse-engineered SMC keys are per-generation.** Verified on M3 Max; other
120+
Pro/Max dies may use different keys. Mitigated: sanity bound + IOReport-first +
121+
Unavailable fallback — never a wrong number, worst case an honest dash.
122+
- **`power.cpu` on Apple Pro/Max is the CPU-cluster sum**, labelled
123+
`CPU complex (SMC)` — it is the P/E cluster rails, close to but not a
124+
vendor-blessed "package" figure.
125+
- **In-process-first costs a redundant `nvidia-smi` on Linux** (in-process, then
126+
the helper) — a few hundred ms per cycle at the collector cadence. Acceptable.
127+
- **No per-core wattage anywhere** — Apple SMC is per-cluster, Intel/AMD RAPL is
128+
per-package/domain. Per-core *utilisation* exists; per-core power does not.
129+
- **Mixed-version fleet** during rollout: an un-updated host emits the old key and
130+
reads the old way until upgraded. Transient, non-breaking.
131+
132+
## 6. Impact
133+
134+
**FinOps:** none — all reads are local, unprivileged where possible; no new
135+
services except the operator-chosen Scaphandre on Windows.
136+
137+
**SRE:** strictly safer. The helper can no longer blank a host under latency; every
138+
gap explains itself; a mis-mapped chip degrades to Unavailable, not garbage.
139+
Recovery for a wrong reading is "it can't happen" (bounded + fallback).
140+
141+
**Security:** all Apple/Linux reads are unprivileged (AppleSMC, IOReport, sysfs
142+
RAPL via the root helper). Windows depends on the operator installing Scaphandre's
143+
signed driver — a documented, user-controlled dependency, not shipped by us.
144+
145+
**Team:** new-chip support = probe the SMC keys on that machine and add them
146+
(§Next Steps). The SMC key catalogue is reverse-engineered and needs occasional
147+
maintenance, exactly like Stats' per-chip temperature keys.
148+
149+
## 7. Decision
150+
151+
Standardize power on `power.cpu` / `power.gpu` / `power.total` across every
152+
platform, sourced by a layered, best-available strategy and a source-aware total
153+
that never double-counts. Read Apple Pro/Max CPU power from the raw SMC cluster
154+
keys (with a sanity bound), read Windows CPU power from a user-run Scaphandre, and
155+
make the helper in-process-first so it is additive, never subtractive. Retire
156+
`power.pkg`; explain every unavailable rail. Backward compatibility is a hard
157+
constraint met by keeping the metric schema stable. Shipped across v2.3.0–v2.5.2
158+
and verified live on Intel/AMD Linux, Windows, and Apple base (M4) + Pro/Max (M3
159+
Max).
160+
161+
Status: **accepted**
162+
163+
## 8. Next Steps
164+
165+
- [ ] Map SMC CPU keys for more Apple dies (M1/M2/M4 Pro/Max, Ultra) as hardware
166+
is available; unmapped chips already fall back safely.
167+
- [ ] Top view: per-core % grid labelled by core type (P/E), plus per-cluster
168+
power where the SMC exposes it.
169+
- [ ] Multi-GPU per-device breakout (currently aggregated).

docs/architecture/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ docs/architecture/NNNN-short-decision-title.md
5656
| [0019](0019-v2-log-search-and-top-sorting.md) | v2: log search and top sorting in the detail modals | accepted | 2026-06-29 |
5757

5858
| [0020](0020-hlidskjalf-top-view-and-npu-rename.md) | Hliðskjálf full-screen top view, NPU rename, and screen-aware TUI | accepted | 2026-06-30 |
59+
| [0021](0021-power-metric-standardization-and-source-layering.md) | Power metric standardization (cpu/gpu/total) and per-platform source layering | accepted | 2026-07-02 |
5960

6061
<!-- Add a row for each ADR as you create them -->

docs/deployment.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,15 @@ you want shows `⚿`.
150150

151151
| Platform | GPU / power without helper? | Helper adds |
152152
|---|---|---|
153-
| Apple Silicon (macOS) | Yes — GPU power + utilisation via IOReport, no root | Full thermal, CPU/ANE power (where the SoC exposes it) |
154-
| Linux + NVIDIA | Yes — `nvidia-smi` is readable unprivileged | Vendor-specific extras |
153+
| Apple Silicon (macOS) | Yes — GPU power + util (IOReport) and CPU/total power (SMC), no root | Full thermal |
154+
| Linux + NVIDIA | Yes — `nvidia-smi` is readable unprivileged | RAPL CPU power (needs root) |
155+
| Windows | Yes — `nvidia-smi` GPU | WMI thermal; CPU power via a user-run Scaphandre |
155156
| Other | Depends on the platform tool | Whatever needs root |
156157

157-
> Note: some Apple Silicon SoCs do not expose a CPU package-power counter at all
158-
> (neither IOReport nor `powermetrics`); CPU power reads as unavailable there.
158+
> Note: on Apple Silicon, `power.cpu` and `power.total` are read without root — the
159+
> daemon reads CPU power from IOReport on base dies and from raw SMC cluster keys on
160+
> Pro/Max/Ultra dies (where IOReport reports 0). Running the helper adds only thermal.
161+
> See [ADR-0021](architecture/0021-power-metric-standardization-and-source-layering.md).
159162
160163
## Networking & ports
161164

@@ -230,4 +233,4 @@ graph TB
230233
| A host never appears | Daemon can't reach the hub | Verify the station IP/port from the host: `nc -vz station 9090`. |
231234
| Metrics show `` (needs-helper) | Privileged metric without a source | Run `sudo heimdall-helper` on that host, or ignore if you don't need it. |
232235
| `Unauthenticated` on the daemon | Token mismatch | Use the same `--token` / `HEIMDALL_TOKEN` on hub, daemon, and dashboard. |
233-
| GPU/CPU power blank on macOS | SoC exposes no counter | Expected on some chips; not a misconfiguration. |
236+
| CPU power blank on macOS | CGO-free release binary — SMC/IOReport power needs a CGO build | Build locally with `make build-tui`, or check `--version` shows a CGO build. Pro/Max CPU power comes from SMC cluster keys mapped per die; an unmapped die falls back to a dash. |

docs/guides/04-privileged-metrics.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ deployment. For the GPU/power specifics of your hardware, jump to:
3131
| **Linux + AMD** (Radeon / Strix Halo) | [Privileged Metrics — Linux + AMD](15-privileged-linux-amd.md) |
3232
| **Windows** | [Privileged Metrics — Windows](16-privileged-windows.md) |
3333

34-
> **Apple Silicon note**: some M-series SoCs do not expose a CPU package-power
35-
> counter at all — neither IOReport nor `powermetrics` reports it. CPU power reads
36-
> as `unavailable` there. That is a hardware limit, not a misconfiguration.
34+
> **Apple Silicon note**: on Pro/Max/Ultra dies IOReport and `powermetrics` report
35+
> `0` for CPU, but Heimdall reads the raw SMC per-cluster keys instead, so
36+
> `power.cpu` is reported there too (see the [macOS guide](13-privileged-macos.md)).
37+
> It only reads `unavailable` on a chip whose SMC keys aren't mapped yet.
3738
3839
## Option A — IOReport (Apple Silicon, no sudo)
3940

@@ -230,11 +231,11 @@ The macOS equivalent of the layout above: the **helper as a root LaunchDaemon**,
230231
and a socket under `/usr/local/var/heimdall`. Both start at **boot** (a LaunchDaemon,
231232
not a per-login LaunchAgent).
232233

233-
> **Apple Silicon build note**: GPU power/util come from **IOReport**, which needs a
234-
> **CGO build** (`make build-tui` locally). The CGO-free release binary falls back to
235-
> `powermetrics` (root). Full thermal and CPU/ANE power come only from the helper.
236-
> Some M-series SoCs expose no CPU package-power counter at all — `power.total` reads
237-
> `unavailable` there. That is a hardware limit, not a misconfiguration.
234+
> **Apple Silicon build note**: GPU power/util and CPU power come from **IOReport**
235+
> and the **SMC**, which need a **CGO build** (`make build-tui` locally). The
236+
> CGO-free release binary falls back to `powermetrics` (root). `power.total` (SMC
237+
> `PSTR`) and `power.cpu` (IOReport on base dies, SMC cluster keys on Pro/Max) are
238+
> always read on Apple Silicon; the helper only adds full thermal.
238239
239240
**1. Shared group** and a stable socket dir the group can traverse:
240241

docs/guides/12-top-view.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fleet, **`q`** to quit.
2323
- **CPU** — utilisation, clock, load average, a trend sparkline, and per-core bars
2424
(coloured by load).
2525
- **MEMORY** — used and swap gauges, a usage trend, and memory bandwidth.
26-
- **POWER**package / CPU / GPU / NPU watts and a power trend.
26+
- **POWER**total / CPU / GPU / NPU watts and a power trend (headlined by `power.total`).
2727
- **GPU / NPU** — utilisation, VRAM, temperature, NPU utilisation.
2828
- **NET & DISK** — rx/tx and read/write with trends.
2929
- **PROCESSES** — top by CPU; the list grows to fill the screen.
@@ -52,10 +52,10 @@ panels look different per machine, and that's expected:
5252
- **swap** — everywhere.
5353
- **cpu.freq**`/sys` cpufreq on Linux (real per-core clock); **``** where no
5454
clock is exposed (e.g. Apple Silicon).
55-
- **GPU** — via `nvidia-smi`; AMD GPUs aren't read yet, so that panel is sparse
56-
on non-NVIDIA hosts.
57-
- **power**package/CPU power needs RAPL (x86 Linux) or SMC (macOS); ARM and
58-
Windows show **``**.
55+
- **GPU**NVIDIA via `nvidia-smi`; AMD via `amd-smi` / amdgpu sysfs on Linux.
56+
AMD on Windows isn't wired yet, so that panel is sparse there.
57+
- **power**`power.cpu` needs RAPL (x86 Linux), SMC (macOS), or a running
58+
Scaphandre (Windows); ARM/GB10 has no CPU power sensor and shows **``**.
5959
- **npu.util / mem.bw** — not collected yet; they render **``** today.
6060

6161
See the [Metrics Reference](../metrics.md) for every metric and its units.

docs/guides/13-privileged-macos.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ guide is the macOS specifics.
1010
|---|---|---|
1111
| `gpu.util`, `power.gpu` | **IOReport** energy counters | no |
1212
| `power.total` (whole-system) | **SMC `PSTR`** ("System Total Power") | no |
13-
| `power.cpu`, `power.npu` | IOReport per-domain, where the SoC exposes it | no |
13+
| `power.cpu` | IOReport per-domain (base dies) or **raw SMC cluster keys** (Pro/Max/Ultra) | no |
14+
| `power.npu` | IOReport ANE per-domain, where the SoC exposes it | no |
1415
| full thermal, gaps IOReport misses | `powermetrics` (via the helper) | yes |
1516

1617
Power standardizes on `power.cpu` / `power.gpu` / `power.total` across the fleet.

docs/metrics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ rather than failing. See [Privileged Metrics](guides/04-privileged-metrics.md).
7272
| `temp.pkg` | °C | CPU package temperature |
7373
| `gpu.temp` | °C | GPU temperature |
7474
| `power.total` | W | whole-machine power — `cpu + gpu (+ npu)`, or the SMC whole-system total on macOS |
75-
| `power.cpu` | W | CPU power — RAPL package (Linux), IOReport CPU (macOS), Scaphandre (Windows, when running); Unavailable-with-reason where no source exists (Apple Pro/Max, Windows without Scaphandre, GB10) |
75+
| `power.cpu` | W | CPU power — RAPL package (Linux), IOReport CPU on Apple base dies + SMC cluster keys on Apple Pro/Max/Ultra, Scaphandre (Windows, when running); Unavailable-with-reason where no source exists (Windows without Scaphandre, ARM/GB10 with no RAPL, an unmapped Apple die) |
7676
| `power.gpu` | W | GPU power |
7777
| `power.npu` | W | NPU / accelerator power (Apple Silicon ANE today). The legacy `power.ane` key is accepted and normalised to `power.npu` on ingest. |
7878
| `npu.util` | percent | NPU utilisation — real on Intel NPUs (`intel_vpu`); Unavailable-with-reason on AMD XDNA and Apple ANE, which expose no counter |

docs/troubleshooting.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
| `Unauthenticated` on the daemon | Token mismatch | Use the same `--token` / `HEIMDALL_TOKEN` on hub, daemon, and dashboard. |
88
| TLS handshake errors | Cert/name mismatch | Ensure clients trust the hub cert (`--tls-ca`) and the name matches (`--tls-server-name` or the cert SAN). |
99
| Metrics show `` (needs-helper) | Privileged metric without a source | Run `sudo heimdall-helper` on that host, or ignore if you don't need it. See [Privileged Metrics](guides/04-privileged-metrics.md). |
10-
| GPU/CPU power blank on macOS | SoC exposes no counter | Expected on some Apple Silicon chips — not a misconfiguration. |
11-
| Power/GPU only with `sudo` | Using a CGO-free release binary | Build from source on macOS for no-sudo IOReport, or run the helper. |
10+
| CPU/total power blank on macOS | CGO-free release binary — SMC/IOReport need CGO | Build from source with `make build-tui`. On Pro/Max/Ultra, CPU power comes from SMC cluster keys mapped per die; an unmapped die shows a dash (honest, not a bug). See [ADR-0021](architecture/0021-power-metric-standardization-and-source-layering.md). |
11+
| CPU power blank on Windows | No Scaphandre running | Windows has no user-space RAPL; run [Scaphandre](https://github.com/hubblo-org/scaphandre) and point `HEIMDALL_SCAPHANDRE_URL` at it. See [Windows guide](guides/16-privileged-windows.md). |
12+
| Power/GPU only with `sudo` | Using a CGO-free release binary | Build from source on macOS for no-sudo IOReport/SMC, or run the helper. |
1213
| `dir.list` refused | Path outside an allow-listed root | The control plane only lists `/var/log`, `/tmp`, etc. by design. |
1314
| Control command refused | Off the allow-list, or bad token | Only built-in keys run; check `--control-token`. |
1415

0 commit comments

Comments
 (0)