v0.23.0 - the scheduler finding that never arrived
The Agentic AI view reported nothing running on its own, on an estate that had just deployed the collectors built to find exactly that. It was not an empty estate and it was not a stale rollout. The finding was being made and then thrown away.
report_once dropped it on the floor
report_once on macOS deduped on surface|tool:
macos : local key="$1|$2" ← the bug
linux : local k="$1|$2|${7:-}"
windows : $key = "$Surface|$Tool|$Trigger"
A scheduled job and an installed tool are the same surface|tool pair. So the moment the account scan reported cli|claude-code, every scheduled-job finding for that tool on that machine was silently discarded — and a machine with a scheduled Claude Code is, necessarily, a machine with Claude Code installed.
The scan has therefore reported nothing on macOS since the day it shipped.
Traced on a real machine, with a decoy launchd job:
+ report_once cli claude-code '' '~/Library/LaunchAgents/zz.probe.plist' \
autonomous '' 'launchd, every 6 hours' interval:21600
+ local 'key=cli|claude-code'
+ case "$SEEN" in
+ return 0 ← dropped here
The control: the same probe for a tool not otherwise present reported perfectly. claude suppressed, gemini reported, the only difference being whether the tool had already been seen.
Linux and Windows have keyed on the trigger since the first day. Only macOS was missed — and macOS is the one collector CI cannot run. There is now a static parity check across all three, which I verified fails on the old key before it passes on the new one.
The version never moved either
The scan shipped without touching COLLECTOR_VERSION, so a collector that reads schedulers and one that cannot both reported 2.0.0. An empty page could not be told from an undeployed one — the only question worth asking about a zero.
It is 2.1.0 everywhere now, and the page states how many devices report a collector new enough to have looked, with 2.0.0 counted as unknown rather than clean, because it genuinely is:
No device reports a collector at 2.1.0 or newer, so nothing here has looked yet. This is an absence, not a finding — update the collectors before reading the zero.
Two overclaims, same family as the last release
Reach was joined per device and drawn per row, so a curl fetching an installer was shown its machine's MCP servers as its own capability — it read as "this curl can reach your Atlassian." The box now says whose they are.
The day panel drew unrecognised rows solid beside genuinely unattended ones, re-merging the two claims the headline was split to separate. Those lanes are hollow now, with a legend entry that says why.
Two silences
The panel rendered nothing at all when no row had a readable schedule, because the "Not drawn" note was nested inside the scheduled.length ? ternary. Twenty-five undrawable rows produced no timeline and no reason — "no cadence" and "never deployed" looked identical. It now explains itself.
A scheduled job that runs a wrapper script cannot be seen by any endpoint-only approach, and reading the contents of somebody's scripts is the same intrusion the page already refuses for prompts. That limit is now stated beside the other things it does not answer. An unstated limit is the same failure as an unstated source.
The registry, finally read whole
Matching used cli.binaries — four tools have any — while app_names and exe_names sat populated and unread for nineteen more. A desktop app reaching its own vendor's gateway was a process nobody could account for, on a finding whose tool had just been resolved from the domain.
- Warp added. Its binary is named for the release channel (
Warp.app/Contents/MacOS/stable), so nothing derives the product fromstable. This was the unexplained process name. - Fireflies gains the desktop names it always shipped under.
- Windows shell and search join the allowlist; they reach
substrate.office.comas background M365 activity. - Generic task hosts and Squirrel's
Update.exejoin the unattributable set instead — naming one says an application did something, not which. An allowlist entry would wrongly read as "this program is fine". - Dotted service names now offer their shorter prefixes, longest first, so
OneDrive.Sync.Service.exematches theOneDrivealready listed — withoutMicrosoft.Notes.exequietly allowlisting everything Microsoft ships. That last one is asserted in a test.
Checked
Ten process names taken from a real estate, replayed through the built registry: one survives, and it is the curl that should.
Both new page states confirmed rendering, not just the data layer — the coverage note and the empty-day-panel explanation. The claude probe verified reporting mode=autonomous on a real machine after the fix, having been silently dropped before it.
Suites green: portal 612, scanner 209, receiver 280, registry 22. Plus collector parity, build.py --check, both chart copies, and shellcheck.
Not in this release
Deliberately, and they need a decision rather than a guess: the API-key-in-job-definition heuristic and the cross-signal join of scheduled executables against DNS-observed processes — both are new detection and belong together with thought about what they would falsely catch. Widening the scheduler matcher to domains is held with them for the same reason: matching a domain in a scheduled command line is real coverage, and it is also a fresh false-positive class one release after cutting one to remove one.