Skip to content

Troubleshooting

Rod Christiansen edited this page Oct 8, 2026 · 3 revisions

Troubleshooting

Failure modes whose cause is visible in the code, with the fix.

ERROR: You must run this as root!

The collector checks getuid() before anything else. Run it with sudo, or let the LaunchDaemons run it.

Invalid API configuration: API URL not configured

ApiUrl is unset or unparseable. Check what the client actually resolved:

sudo defaults read /Library/Preferences/com.github.reportmate ApiUrl

Remember the precedence: a profile-forced value beats the system plist, and the system plist beats an environment variable. Only --api-url on the command line beats a profile. The run banner prints the effective ApiUrl under === FINAL CONFIGURATION ===.

To see whether a profile forces a value, read the managed preferences file.

sudo defaults read "/Library/Managed Preferences/com.github.reportmate"

HTTP error 401 on transmission

Neither X-API-Key nor X-Client-Passphrase was accepted. Both headers are sent when both are configured, and the API prefers the key and falls through to the passphrase — so a bad ApiKey alone is enough to fail if no valid passphrase is set. Set Passphrase (and ApiKey, if your deployment uses one) and rerun.

The management module reports nothing, or the run stalls in it

The osquery managed_policies table reads TCC-protected plists under /Library/Managed Preferences/. Without Full Disk Access, osqueryi hangs in a TCC check with no UI to resolve, and the query eventually hits its timeout. The macadmins extension has the same problem: it runs as its own process with its own TCC identity, and without an independent grant it hangs in a sandbox check while osqueryi blocks on its socket.

Deploy ReportMate-FullDiskAccess.mobileconfig with your Team ID substituted for TEAMID0000. Grants for io.osquery.agent and macadmins_extension are just as necessary as the one for the client itself.

A device keeps reporting an MDM provider it no longer uses

Every step of the management module is isolated: a step that fails records an entry in management._collection_errors and returns an empty default rather than failing the module. A management payload that never arrives leaves the server showing the previous record — which is how a re-enrolled Mac can report its old provider indefinitely. Check _collection_errors in the cached management.json before assuming the device really is on the old MDM.

Whole groups of Macs appear to reboot at once

Reboot detection compares the kernel's boot time, read from sysctl kern.boottime and published at systemDetails.bootTime, with the previous run's value, with a 300-second tolerance for NTP clock corrections. A boot time derived as now - uptime would slide forward by the length of each run and make every run look like a reboot, so the client uses that value only as a fallback. If you see this, confirm the system module is reporting systemDetails.bootTime; when it is missing, the log says No kernel boot time reported, falling back to uptime-derived boot time.

Every module reports shell data only, or the log says No trusted osquery found

The runner only launches an osquery binary that resolves to a root-owned file in a directory chain nobody else can write, and that carries osquery's own Developer ID signature. A Homebrew osquery under /opt/homebrew, a copy in a user-writable folder, or a self-built binary is skipped, and with no trusted candidate every module uses its shell fallback. Install the official osquery pkg. See Architecture for the full search order.

A warning that names osquery extension at <path> failed ownership or signature checks means the same for the macadmins extension. In a signed build the extension must also be signed with the runner's Team ID, which build.sh --sign does to the bundled copy; an upstream copy placed by hand is refused.

A module returns nothing and the log says the query timed out

Each osquery query races a timer — 30 s for built-in tables, 60 s for extension tables — after which the child is SIGTERMed and then SIGKILLed. Extension queries get the longer budget because the extension has a startup delay plus registration time, and some of its tables do online I/O; sofa_unpatched_cves fetches the SOFA feed. On a slow link, raise the budget:

sudo defaults write /Library/Preferences/com.github.reportmate ExtensionQueryTimeoutSeconds -int 120

To take the extension out of the picture entirely and rely on shell fallbacks:

sudo defaults write /Library/Preferences/com.github.reportmate ExtensionEnabled -bool false

Wi-Fi SSID reports as [Location Services Required]

macOS withholds the current SSID without Location Services. The client substitutes the first known network and adds "note": "SSID from known networks (location services disabled)" to currentWiFiNetwork. Enable Location Services for the collector if you need the real value.

Collections are slow

The network module's throughput test measures actual bandwidth and takes 20–30 seconds every time that module runs. The hardware module's deep storage analysis walks the root volume with du. The hourly daemon already passes --storage-mode quick; if you are running the module by hand, pass it too.

swift build cannot find ModuleVersions or AppVersion

Sources/Generated/ModuleVersions.swift is gitignored, so a fresh clone has no ModuleVersions and a plain swift build fails. Sources/Core/AppVersion.swift is committed as a placeholder, but an interrupted build can leave it truncated. build.sh generates both and restores compilable placeholders when it exits, so run it once without packaging.

./build.sh --skip-pkg --skip-zip --skip-dmg

build.sh: bad interpreter: /opt/homebrew/bin/bash

The script requires Homebrew's bash 5.

brew install bash

A daemon never runs and bootstrap fails

launchctl disable is recorded in launchd's own override database, keyed by label, independent of the plist file. Removing and reinstalling the plist does not clear it and bootstrap keeps failing. The postinstall runs launchctl enable system/<label> before bootstrapping for exactly this reason; if you disabled a daemon manually, re-enable it the same way.

sudo launchctl enable system/com.github.reportmate.hourly

Watch for WARNING: failed to load <label> in the install log — a daemon that silently fails to load is a module that silently stops being collected.

Munki runs but installs data never updates

The postflight chain has three parts. Check all of them:

ls -l /usr/local/munki/postflight /usr/local/munki/postflight.d/
sudo tail -50 "/Library/Managed Reports/logs/reportmate-postflight.log"

reportmate.sh does not run the collection itself — it kickstarts system/com.github.reportmate.installs. Munki allows a postflight only 60 seconds and launchd kills the whole process group when the run ends, so a child started inline cannot survive. If the kickstart line logs an error, confirm the installs daemon is loaded.

Scripts in postflight.d/ must be executable; the wrapper logs and skips anything that is not.

Another reporting tool stopped submitting after installing ReportMate

The postinstall moves an existing postflight into postflight.d/ under a name matching what it looked like (munkireport.sh, sal.sh, original.sh) and runs it from there in name order. Confirm it is present and executable.

sudo chmod 755 /usr/local/munki/postflight.d/munkireport.sh

The cache directory is enormous

Each run prunes to the 24 newest timestamped directories. If you see hundreds, the client on that Mac does not include the prune; upgrade it.

GUI cannot save settings or run a collection

Check these in order:

  1. The key is forced by a configuration profile. The GUI shows it locked, and the helper refuses to write or remove it.
  2. The key is not one the Prefs tab edits. The helper writes only those thirteen keys; set the others with defaults or a profile.
  3. The helper rejected the connection. It accepts only the app with identifier com.github.reportmate signed by the helper's own Team ID, so an unsigned or ad-hoc build, or an app signed with a different identity than the helper, is refused.
  4. The helper daemon is not loaded. Without it the Run tab falls back to a direct, unprivileged run that fails the root check.

The helper logs each decision to the unified log.

log show --last 1h --predicate 'subsystem == "com.github.reportmate.helper"'

Transmission failed after several attempts

A dropped connection, timeout or HTTP 408, 429 or 5xx is retried up to MaxRetryAttempts times (default 3) with 1 s and 2 s backoff, and each retry is logged as Transmission attempt N/M failed. A run that still fails leaves its payload in the cache, so it can be re-sent once the API is reachable.

sudo managedreportsrunner --transmit-only

Clone this wiki locally