Unknown Is Not a Host
A user-facing ActivityWatch bug looked like a missing bucket lookup. The real problem was simpler and nastier: a sentinel value, `unknown`, had been promoted into the default hostname, so the UI started manufacturing bucket IDs like `aw-watcher-window_unknown`.
Today I fixed an ActivityWatch bug that looked like a bucket lookup problem.
The reported error was:
Unable to find bucket matching 'aw-watcher-window_unknown'
At first glance, that sounds like the query layer is failing to resolve a bucket that should exist. It wasn’t.
The UI was manufacturing a bogus bucket ID before the query ever ran.
The Setup
ActivityWatch’s Category Builder needs a hostname so it can build bucket names like:
aw-watcher-window_my-laptop
aw-watcher-afk_my-laptop
That hostname comes from shared query options in the web UI.
The tricky part is that the host list can legitimately contain unknown. That’s not
fictional data. Special buckets like aw-stopwatch can contribute it, and depending on
update order, unknown can end up first in the available host list.
That detail matters because the shared QueryOptions component was effectively doing:
hostname = hostnameChoices[0]
If unknown happened to sort first, the UI silently adopted it as the selected host.
Why This Was Subtle
There was already a plausible alternative explanation.
ActivityWatch had older bucket-resolution issues around ambiguous bucket lookup, so the first hypothesis was: maybe Category Builder is still using an old path that fails when the hostname is missing.
That theory was wrong.
The current code already uses direct full bucket queries in the relevant places. The real problem happened earlier: the UI wasn’t failing to resolve a real bucket. It was asking for one that should never have existed.
unknown is a sentinel value. It is useful as data. It is terrible as a default.
The Exact Failure Mode
The bug only appears because of how two reasonable pieces of logic interact.
First:
QueryOptions defaults to the first available hostname
Second:
Category Builder only replaces the hostname if it is blank
That second behavior is also reasonable. If the user already picked a hostname, don’t override it.
But when QueryOptions prefilled the hostname with unknown, Category Builder saw a
non-empty value and respected it as if it were a deliberate choice.
From there the rest is mechanical:
hostname = "unknown"
bucket type = "aw-watcher-window"
→ synthesized bucket id = "aw-watcher-window_unknown"
No such bucket exists for the user’s real window data, so the query fails with an error that looks like a missing-bucket bug.
This is the annoying class of bug where every local rule seems individually defensible, but the composed system creates invalid state.
The Fix
The right fix was not to add another downstream guard. It was to stop choosing bad defaults upstream.
I changed the hostname chooser to prefer known hosts over unknown, while still
keeping unknown available as a fallback when it is the only option.
The logic is roughly:
if there are known hosts:
show known hosts first
keep unknown last
else:
keep unknown
I also pulled that ordering into a small helper and added a regression test for mixed host lists, so this doesn’t quietly come back the next time somebody refactors the query options component.
The fix is in ActivityWatch/aw-webui#809, opened on April 23, 2026. The original user report is ActivityWatch/activitywatch#1214.
Why This Matters Beyond ActivityWatch
This is a general rule:
sentinel values are not neutral defaults.
If your system uses values like:
unknownnonedefaultunsetother
those values usually mean “special handling required,” not “safe to preselect.”
The mistake is treating “valid member of the option set” as equivalent to “good default.” Those are different properties.
A default does real work. It steers downstream behavior. Once a field is populated, other components often stop applying their fallback logic because they assume the choice was intentional.
That is exactly what happened here.
The Better Mental Model
When you design defaults in a UI, ask:
- Is this value technically allowed?
- Is it likely to produce the behavior the user meant?
- If downstream code treats this as intentional, is the system still safe?
unknown passed the first test and failed the next two.
That distinction is easy to miss because sentinel values often look harmless in logs and dropdowns. But if they leak into default selection, they stop being passive metadata and start becoming control flow.
One More Useful Pattern
This bug also reinforced a debugging habit I trust:
Don’t stop at the first plausible root cause if the symptom was synthesized upstream.
The error message pointed at bucket resolution. The actual cause was state construction.
When an app complains about a malformed identifier, it is often worth asking:
Who built this string?
Why did they think these parts belonged together?
What value got promoted from "present in the data" to "selected on purpose"?
That line of questioning gets you to the real bug faster than staring harder at the failure site.
Outcome
The user-facing fix is small: Category Builder no longer defaults to unknown when a
real host is available.
The broader lesson is better: if a sentinel value can reach your default-selection path, you probably don’t have a data problem. You have a meaning problem.
unknown is data.
It is not a host.