BUGS.md: fix pass outcomes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-18 17:53:05 -07:00
co-authored by Claude Fable 5.1
parent c1805f6ad5
commit 44e85b768f
+22 -22
View File
@@ -1,6 +1,6 @@
# Bug bash — 2026-09-18 # Bug bash — 2026-09-18
Discovery only; nothing here is fixed yet. Found by a headless Chromium pass (iPhone and Fix pass 2026-09-18: every item carries its outcome; ✅ resolved and verified live, ➖ no change with reason. Found by a headless Chromium pass (iPhone and
1440px desktop viewports) over every page on the hub and on each remote machine, an 1440px desktop viewports) over every page on the hub and on each remote machine, an
offline-node scenario (wyse stopped for the test, then restarted), direct API probing offline-node scenario (wyse stopped for the test, then restarted), direct API probing
with hostile inputs, and a code read. Ordered by severity within each group. with hostile inputs, and a code read. Ordered by severity within each group.
@@ -27,16 +27,16 @@ with hostile inputs, and a code read. Ordered by severity within each group.
5. ✅ **RESOLVED** — kernel threads (empty cmdline) and the daemon's own PID are refused. **Signals can be sent to kernel threads.** `POST /api/procs/2/signal` (kthreadd) 5. ✅ **RESOLVED** — kernel threads (empty cmdline) and the daemon's own PID are refused. **Signals can be sent to kernel threads.** `POST /api/procs/2/signal` (kthreadd)
returns 204. Harmless in practice, but the guard should refuse PIDs with an empty returns 204. Harmless in practice, but the guard should refuse PIDs with an empty
cmdline (kernel threads) and our own PID. cmdline (kernel threads) and our own PID.
6. **Journal spam from the daemon's own sudo calls.** Every sudo'd `journalctl`, `smartctl` 6. ✅ **RESOLVED** — unit runs with `SupplementaryGroups=systemd-journal adm` so journal reads need no sudo, and the installer drops `/etc/sudoers.d/hamlab-quiet` (no syslog/pam session for the read-only polling commands). Verified: 0 sudo lines in 60 s of 250 ms polling (was 839/10 min). Applied on all four hosts. **Journal spam from the daemon's own sudo calls.** Every sudo'd `journalctl`, `smartctl`
or `pwrstat` writes three `pam_unix(sudo:session)` lines into the journal *under or `pwrstat` writes three `pam_unix(sudo:session)` lines into the journal *under
`hamlab.service`*: 839 lines in 10 minutes at a 250 ms refresh, and the journal is `hamlab.service`*: 839 lines in 10 minutes at a 250 ms refresh, and the journal is
already 4 GB. It also buries Ham Lab's own log entries. Fix options: add the service already 4 GB. It also buries Ham Lab's own log entries. Fix options: add the service
user to `systemd-journal` (and `adm`) so journal reads don't need sudo, and/or a user to `systemd-journal` (and `adm`) so journal reads don't need sudo, and/or a
sudoers `Defaults!/usr/bin/journalctl !syslog, !pam_session` line in the installer. sudoers `Defaults!/usr/bin/journalctl !syslog, !pam_session` line in the installer.
7. **Uploading over an existing file silently overwrites it.** No confirmation, no 7. ✅ **RESOLVED** — server answers 409 unless `overwrite=1`; the UI asks "Replace it?" first. **Uploading over an existing file silently overwrites it.** No confirmation, no
"replace?" prompt. Same for "+ file" when the name exists (server refuses; UI just "replace?" prompt. Same for "+ file" when the name exists (server refuses; UI just
shows the error, fine) — upload is the gap. shows the error, fine) — upload is the gap.
8. **`/api/<anything>/../../x` falls through to the SPA** and returns `index.html` with 8. ➖ **NO CHANGE** — Go's mux redirects the unclean path to `/etc/passwd`, which is an ordinary deep link the SPA must serve; there is no API prefix left by the time the static handler sees it. Harmless. **`/api/<anything>/../../x` falls through to the SPA** and returns `index.html` with
200 instead of a JSON 404. Harmless, but sloppy for an API prefix. 200 instead of a JSON 404. Harmless, but sloppy for an API prefix.
## Functional bugs ## Functional bugs
@@ -44,38 +44,38 @@ with hostile inputs, and a code read. Ordered by severity within each group.
9. ✅ **RESOLVED** — 401 carries the server message; the form shows "wrong password" (verified headless). **Wrong password gives no feedback at all.** The 401 is thrown as `Unauthenticated` 9. ✅ **RESOLVED** — 401 carries the server message; the form shows "wrong password" (verified headless). **Wrong password gives no feedback at all.** The 401 is thrown as `Unauthenticated`
with an empty message, and the login form only renders `err` when non-empty. The with an empty message, and the login form only renders `err` when non-empty. The
button just re-enables. Reproduced on phone and desktop. button just re-enables. Reproduced on phone and desktop.
10. **Switching machines while on Settings or Machines keeps showing the hub's page.** 10. ✅ **RESOLVED** — hub-only pages bounce to the dashboard on switch (verified headless). **Switching machines while on Settings or Machines keeps showing the hub's page.**
`switchMachine` only resets the view for Machines; Settings stays, so "mac" shows `switchMachine` only resets the view for Machines; Settings stays, so "mac" shows
core's account/token/about panels. Both are hub-only pages and should bounce to the core's account/token/about panels. Both are hub-only pages and should bounce to the
dashboard (or be clearly labelled as hub pages). dashboard (or be clearly labelled as hub pages).
11. **Machines add-form `pattern` is an invalid regex in Chromium.** `[a-z0-9][a-z0-9-]*` 11. ✅ **RESOLVED** — escaped; no console error. **Machines add-form `pattern` is an invalid regex in Chromium.** `[a-z0-9][a-z0-9-]*`
fails under the `v` flag ("Invalid character class"), logs a console error and the fails under the `v` flag ("Invalid character class"), logs a console error and the
browser skips validation. Needs `[a-z0-9][a-z0-9\-]*`. browser skips validation. Needs `[a-z0-9][a-z0-9\-]*`.
12. **Updates tab on dnf hosts shows nonsense.** Mac: "Index refreshed 561d ago" (mtime of 12. ✅ **RESOLVED** — refresh from the newest repodata under libdnf5/dnf caches (Mac: 9 d), last upgrade from `dnf history` (194 d), label reads "from dnf history". **Updates tab on dnf hosts shows nonsense.** Mac: "Index refreshed 561d ago" (mtime of
`/var/cache/dnf`, which Fedora 41+ no longer uses; `libdnf5` is the live cache) and `/var/cache/dnf`, which Fedora 41+ no longer uses; `libdnf5` is the live cache) and
"Last upgrade never · from apt history" (label and source are apt-specific). Needs "Last upgrade never · from apt history" (label and source are apt-specific). Needs
`dnf history` for last upgrade, metadata timestamps for refresh, and neutral labels. `dnf history` for last upgrade, metadata timestamps for refresh, and neutral labels.
13. **Cron overview is slow: 3.6 s per request on core.** At any refresh rate the page 13. ✅ **RESOLVED** — journal read incrementally by cursor, timers from one `systemctl show`, services' two calls run in parallel. Cron 3.6 s → 0.08 s, services 0.6 s → 0.05 s, with last-run and next-run data intact. **Cron overview is slow: 3.6 s per request on core.** At any refresh rate the page
is constantly busy. The 30-day, 20 000-line journal scan for "last run" is rebuilt is constantly busy. The 30-day, 20 000-line journal scan for "last run" is rebuilt
every 5 s; timers make three `systemctl` calls. Needs an incremental journal cursor every 5 s; timers make three `systemctl` calls. Needs an incremental journal cursor
and a single `systemctl` call. Services list is 0.6 s for the same reason (two calls). and a single `systemctl` call. Services list is 0.6 s for the same reason (two calls).
14. **Daemon memory grows.** RSS on core went 22 → 85 → 216 MB during the day (136 MB at 14. ✅ **RESOLVED** — heap profile showed a 48 MB `bytes.Buffer` from buffering `journalctl` dumps; those are now streamed line by line, plus a 160 MB soft memory limit for the GC. RSS 22 MB fresh, 27 MB after 25× cron/logs/procs/services (was 180 MB). pprof stays available behind the login at `/api/debug/pprof/`. **Daemon memory grows.** RSS on core went 22 → 85 → 216 MB during the day (136 MB at
rest after the bash). Suspects: per-pid caches in the process table (`procInfo` only rest after the bash). Suspects: per-pid caches in the process table (`procInfo` only
prunes on the next walk), task output buffers, SMART cache, xterm scrollback rings prunes on the next walk), task output buffers, SMART cache, xterm scrollback rings
that are never freed for exited sessions. Needs a heap profile under load. that are never freed for exited sessions. Needs a heap profile under load.
15. **Version string is always `-dirty`.** `make deploy` builds before the commit, so 15. ✅ **RESOLVED** — `--dirty` dropped from the version and deploys now follow commits; binaries report e.g. `v0.1.0-38-gc1805f6`. **Version string is always `-dirty`.** `make deploy` builds before the commit, so
every installed binary reports e.g. `v0.1.0-32-ga372378-dirty`, and the Machines page every installed binary reports e.g. `v0.1.0-32-ga372378-dirty`, and the Machines page
compares with `-dirty` stripped. Build after commit, or tag releases. compares with `-dirty` stripped. Build after commit, or tag releases.
16. **View resets to Dashboard on reload; no deep links; browser Back leaves the app.** 16. ✅ **RESOLVED** — hash routing `#/<machine|hub>/<view>`: reload restores machine and tab, Back/Forward work, links can point at a page on a machine (verified headless). **View resets to Dashboard on reload; no deep links; browser Back leaves the app.**
The selected machine survives a reload (sessionStorage) but the tab does not, and The selected machine survives a reload (sessionStorage) but the tab does not, and
there is no URL routing at all. Needs hash or path routing (`/#/grid/services`). there is no URL routing at all. Needs hash or path routing (`/#/grid/services`).
17. **Cron log tab lists oldest first and starts at the top.** 200 lines from Sep 10 with 17. ✅ **RESOLVED** — newest first. **Cron log tab lists oldest first and starts at the top.** 200 lines from Sep 10 with
the newest at the bottom, off-screen. Should show newest first or scroll to the end. the newest at the bottom, off-screen. Should show newest first or scroll to the end.
18. **Processes: empty filter result shows nothing.** No "nothing matches" message. 18. ✅ **RESOLVED** — "Nothing matches." **Processes: empty filter result shows nothing.** No "nothing matches" message.
19. **Terminals accumulate.** Nothing ever reaps exited or idle sessions; the bash left 19. ✅ **RESOLVED** — exited shells remove themselves 10 minutes after exit (verified live: seeded 17:49, gone by the check). Idle running shells are deliberately kept. **Terminals accumulate.** Nothing ever reaps exited or idle sessions; the bash left
several "bug-bash-term" shells on the hub until deleted by hand. Exited sessions several "bug-bash-term" shells on the hub until deleted by hand. Exited sessions
should auto-remove after a while; idle ones at least be counted somewhere. should auto-remove after a while; idle ones at least be counted somewhere.
20. **Self-update restarts without warning running work.** A push to a node kills its 20. ✅ **RESOLVED** — a node answers 409 "busy" when a package task is running or a terminal is attached; the Machines page offers Force, `hamlab push-nodes --force` / `make push-nodes-force` do the same. Verified: 409 with a terminal attached on grid, 200 with force. **Self-update restarts without warning running work.** A push to a node kills its
open terminals and any running task (upgrade in progress). The hub should refuse or open terminals and any running task (upgrade in progress). The hub should refuse or
warn when the node reports a running task or attached terminals. warn when the node reports a running task or attached terminals.
21. ✅ **RESOLVED** — PathError and LinkError are unwrapped to the bare reason. **Error text leaks raw Go errors.** e.g. rename shows "rename /tmp/x /tmp/y: no such 21. ✅ **RESOLVED** — PathError and LinkError are unwrapped to the bare reason. **Error text leaks raw Go errors.** e.g. rename shows "rename /tmp/x /tmp/y: no such
@@ -83,23 +83,23 @@ with hostile inputs, and a code read. Ordered by severity within each group.
## Mobile / UX ## Mobile / UX
22. **Tap targets under 28 px on phone:** header ⏻ (25), logout (22), customize ▲ ▼ hide 22. ✅ **RESOLVED** — on coarse pointers every button, chip, icon button, link, header and select is at least 36 px tall (verified: no control under 32 px on the dashboard or processes pages). **Tap targets under 28 px on phone:** header ⏻ (25), logout (22), customize ▲ ▼ hide
(23), terminal leaf ✎ ✕ (24), chips (25), table headers (27). Apple's guideline is (23), terminal leaf ✎ ✕ (24), chips (25), table headers (27). Apple's guideline is
44 pt. Systemic: the small `.btn`/`.chip`/`.icon-btn` sizes need a touch variant. 44 pt. Systemic: the small `.btn`/`.chip`/`.icon-btn` sizes need a touch variant.
23. **Power dialog on the Mac asks you to type `5ce91ee54af6`.** The confirmation uses 23. ✅ **RESOLVED** — the dialog asks for the machine name (`mac`); the node still checks its own hostname under the hood. **Power dialog on the Mac asks you to type `5ce91ee54af6`.** The confirmation uses
the node's hostname, which on the Mac is the unset default. Should offer the machine the node's hostname, which on the Mac is the unset default. Should offer the machine
name (`mac`) as the confirmation word on remote machines. name (`mac`) as the confirmation word on remote machines.
24. **Dashboard shows dead interfaces and empty modules.** wlp1s0 (down) gets a full 24. ✅ **RESOLVED** — down interfaces collapse to one line marked "down"; modules with nothing to show (fans, UPS, temps, disks) hide unless customizing. **Dashboard shows dead interfaces and empty modules.** wlp1s0 (down) gets a full
sparkline block of flat lines; "Fans: no fan sensors" is a permanent empty card on sparkline block of flat lines; "Fans: no fan sensors" is a permanent empty card on
hosts without fans. Down interfaces should collapse to one line; empty modules should hosts without fans. Down interfaces should collapse to one line; empty modules should
auto-hide unless customizing. auto-hide unless customizing.
25. **Header load average is unlabelled** ("1.39 1.56 1.55" floating in the header). 25. ✅ **RESOLVED** — "load 1.39 1.56 1.55" with a tooltip. **Header load average is unlabelled** ("1.39 1.56 1.55" floating in the header).
26. **Files list on desktop is a narrow strip in a sea of empty space** for small 26. ➖ **NOT FIXING** — the list is intentionally a plain table; the menu anchors inside the row. Cosmetic taste, not a defect. **Files list on desktop is a narrow strip in a sea of empty space** for small
folders; the row menu opens off the right edge of the row. Cosmetic. folders; the row menu opens off the right edge of the row. Cosmetic.
27. **Offline machine keeps polling every view at the refresh rate**, producing a 502 27. ✅ **RESOLVED** — after a 502 the client leaves that machine alone for 5 s: 2 requests in 10 s at a 250 ms refresh (would have been ~80), badge and message intact. **Offline machine keeps polling every view at the refresh rate**, producing a 502
per request per page (and console errors). The UI copes — badge, message — but the per request per page (and console errors). The UI copes — badge, message — but the
hub should back off to the prober's cadence while a node is down. hub should back off to the prober's cadence while a node is down.
28. **Services logs are noise.** The default view of `hamlab.service` logs is the sudo 28. ✅ **RESOLVED** with 6. **Services logs are noise.** The default view of `hamlab.service` logs is the sudo
session spam from bug 6, three lines per poll. session spam from bug 6, three lines per poll.
## Confirmed working under abuse (for the record) ## Confirmed working under abuse (for the record)