Skip to content

Commit 2e33608

Browse files
authored
Refactor temperature reading, improve error handling, and optimize CPU usage (#3)
* sketchybar: read die temperature from a helper, not macmon macmon's temp.cpu_temp_avg is not trustworthy. Measured inside a SINGLE invocation with CPU load flat at 1.5-3.7% and cpu_power at 0.06W: t+0s cpu=37.74 gpu=35.33 t+2s cpu=31.79 gpu=35.27 t+4s cpu=20.19 gpu=35.27 A die cannot cool 17C in two seconds and reheat. Across trials the value is bimodal, landing on ~31.9 or ~38.1 and never between, which is the signature of a mean over a varying sensor set rather than a temperature. helpers/thermal.swift reads the PMU tdie sensors directly through IOHIDEventSystemClient (no sudo). Spread 0.30C over 20 reads, and it tracks real load: 34.9 idle -> 37.6 under full load, monotonic. The helper was untracked until now, while both install and system.sh depended on it. Both guarded defensively, so a fresh clone produced a bar with no temperature and no error at all. * install: make a missing helper source loud instead of silent The two helper build blocks tested `[[ -r $src ]] && command -v swiftc` in one condition, so a MISSING SOURCE and a MISSING TOOLCHAIN were indistinguishable and both exited without printing anything -- no ok, no warn. thermal.swift was untracked in git, so a fresh clone hit exactly that path and produced a bar with no temperature and no explanation. Replace both with one build_sb_helper function carrying a three-way guard, so each outcome reports itself. * sketchybar: document weather's IP geolocation, and fail closed on HTTP errors sketchybarrc pointed at "the privacy note in weather.sh"; no such note existed. With LOCATION empty, wttr.in geolocates by SOURCE IP, so every poll discloses the host's public IP to a third party. That is a fair trade for zero configuration but it is not visible from the URL. Write the note, and add SKETCHYBAR_WEATHER_LOCATION so the behaviour can be pinned to a place or turned off. Also curl -sf: without -f, curl exits 0 on a 5xx and the error page is written to the cache, which is then served as the last-known-good reading indefinitely. * aerospace: float Raycast; the "not installed" comment had gone stale The rule was commented out on the belief that Raycast was in the Brewfile but not installed. /Applications/Raycast.app exists and its Info.plist gives com.raycast.macos -- exactly the id the comment declined to guess. Caveat recorded inline: the id came from Info.plist, not from `aerospace list-apps` with the Settings window open, since Raycast was not running. * sketchybar: fix volume and brightness reporting values they cannot know volume showed "missing value%". osascript prints that literal string and exits 0 for a device with no software volume, so the `[ -z "$vol" ]` guard sailed past it. The output here is a Focusrite Scarlett 2i2, which exposes neither kAudioDevicePropertyVolumeScalar nor Mute on any scope or element -- confirmed against CoreAudio. There is genuinely no level to show. brightness showed a constant 100%. It looped display ids 1..16 and took the first DisplayServicesGetBrightness that answered; on this clamshell desk that is the external, and the call SUCCEEDS for a non-Apple external and returns a hardcoded 1.0. The 100% was fabricated, not stale. Both now read the right source (brightness: gamma ramp for an external, which is what MonitorControl's software dimming manipulates -- verified 0.75/0.50/0.25 -> 75/50/25%) and hide rather than invent a number. colors.sh gains the compositing note left over from a frosted-bar experiment that was reverted. * docs: record the silent-failure rules, and add the production audit Three failure classes that are invisible when they occur, all found the hard way: - a self-hiding item under the config-wide updates=when_shown default stops being updated entirely, so it can never un-hide. Eight items were affected; each worked after a reload and then went quiet. - SketchyBar fork_execs plugins and reports NOTHING when the execute bit is missing. The item simply never updates. - a plugin runs with less TCC access than a terminal, so ~/Library reads succeed by hand and fail with EPERM in the bar, at correct Unix permissions. docs/audit-2026-08-02.md is a full production-readiness review with 20 findings, measured costs per plugin, and a four-phase roadmap. * sketchybar: cut idle CPU by raising two intervals that were set on bad data mic was polled every 2s on the strength of a comment in mic.swift saying the helper "runs in single-digit ms". Measured min/median/max over 10 consecutive runs: 60/63/65ms -- wrong by a factor of 8. At 2s that item alone cost 1890ms of CPU per MINUTE, 36% of the whole bar's budget and the single most expensive thing in the config. Now 5s. The cost is inherent, not a Swift artefact: the helper enumerates every CoreAudio device and queries two properties each, so it is O(devices) IPC into coreaudiod per tick. Making it free means a property listener, not a faster loop; noted in the source. system was 30s at ~930ms per invocation (macmon alone measured 854ms, because it samples a full SoC telemetry frame to extract three numbers). Now 60s. CPU%, RAM and fan RPM are not acted on faster than that. Together: 3750 -> 1686 ms/min, a 55% reduction in the bar's idle CPU. * sketchybar: repaint all workspace pills from one query, not nine Each of the nine workspace items ran its own copy of aerospace.sh, and each copy shelled out to `aerospace list-workspaces` to ask a GLOBAL question -- which workspaces hold windows -- whose answer is identical for all nine. Measured 25ms per call, 104ms for the nine, plus nine greps. Worse, the items were subscribed to front_app_switched as well as aerospace_workspace_change, so all of that ran on every application switch, one of the highest-frequency actions there is. Replace with a zero-width driver item that owns the subscriptions and rewrites every pill in a single sketchybar call: 2 aerospace calls and 1 sketchybar call regardless of workspace count. Full repaint now 54ms. The occupancy test is parameter expansion instead of a grep per item, with sentinel spaces so a future workspace 10 cannot match workspace 1. * portability: fall back to /usr/local for Intel Homebrew Both PATH exports hardcoded /opt/homebrew, the Apple Silicon prefix. On an Intel Mac every plugin depending on aerospace, macmon, gh or icalBuddy would fail to find its binary -- and they all hide rather than error when that happens, so the bar would come up merely looking quiet. Prepending a directory that does not exist is harmless, and this avoids a `brew --prefix` subprocess on every bar start. * sketchybar: extract lib.sh and collapse six duplication sites hide() was defined identically in three plugins, the truncate idiom was copy-pasted into three more, the helper build-on-demand block existed twice, and amphetamine.sh had a six-line osascript block duplicated verbatim inside itself. lib.sh provides hide, truncate_label, state_file, ensure_helper and require. Notes on two of them: hide() now closes any popup unconditionally. Two of the three local definitions did that and the third has no popup; popup.drawing=off on a popup-less item is accepted silently (verified). Doing it always removes a footgun -- a bare hide() that left a popup on screen would be a rare and subtle bug. truncate_label replaces `printf | cut -c1-N`, a two-subprocess pipeline run on every invocation of every plugin that shows a name. cut -c counts BYTES in some implementations while ${#s} and ${s:0:n} count characters, so the old form could cut a UTF-8 name mid-codepoint. require() gives missing dependencies a voice: plugins previously failed into empty output and then hid, which is indistinguishable from "nothing to report". * sketchybar: updates=on for music -- the ninth self-hiding item Found by scripts/lint-sketchybar.sh, not by the hand audit that claimed to have found all of them. music.sh hides via a hide() call rather than a literal `--set "$NAME" drawing=off`, so the grep used to sweep for this never saw it. The item was live-broken: Apple Music is usually not running, so the item was sitting hidden with updates=when_shown, meaning starting playback would never have brought the pill back. * ci: lint the failure classes that are invisible at runtime Three SketchyBar behaviours produce no error and no log line, so the only symptom is an item that quietly stops updating: - a plugin without its execute bit; sketchybar fork_execs it and says nothing - a self-hiding item without updates=on, which can then never un-hide - a comment referencing a section that no longer exists, in a config where the comments carry the measurements scripts/lint-sketchybar.sh checks all three, and earned its place immediately by finding the music item bug above. It is deliberately precise about two things it must NOT flag: label.drawing=off and background.drawing=off hide a COMPONENT, leaving the item drawn and updating (amphetamine and pomodoro both do this correctly); and passive items like cpu and memory carry no script of their own, so they are un-hidden by whichever item's script writes them. * Update
1 parent b378f6f commit 2e33608

33 files changed

Lines changed: 1083 additions & 940 deletions

.github/workflows/lint.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: lint
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
jobs:
10+
shell:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Install shellcheck
16+
run: sudo apt-get update && sudo apt-get install -y shellcheck
17+
18+
# The plugins and the install script. SC1091 is "can't follow source",
19+
# which is expected: they source $HOME/.config/sketchybar/*.sh, a path that
20+
# only exists once stow has run.
21+
- name: shellcheck
22+
run: |
23+
shellcheck -S warning -e SC1091 \
24+
sketchybar/.config/sketchybar/*.sh \
25+
sketchybar/.config/sketchybar/plugins/*.sh \
26+
raycast/.config/raycast/scripts/*.sh \
27+
scripts/*.sh
28+
29+
# install is zsh, not bash — shellcheck cannot read it, so just parse it.
30+
- name: zsh parse install
31+
run: |
32+
sudo apt-get install -y zsh
33+
zsh -n install
34+
35+
sketchybar:
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v4
39+
40+
# Catches the SketchyBar-specific failures that are invisible at runtime:
41+
# a plugin without its execute bit, a self-hiding item missing updates=on,
42+
# and a comment pointing at a section that no longer exists. None of these
43+
# produce any error on the host — the item simply never updates.
44+
- name: sketchybar semantics
45+
run: ./scripts/lint-sketchybar.sh

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# Anchored with a leading slash so it matches only the repo root, never the
1717
# tracked `claude/` stow package.
1818
/.claude
19+
CLAUDE.local.md
1920

2021

2122
# ============================================================

Brewfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ brew "eza"
3333
brew "fzf"
3434
brew "jq"
3535
brew "zoxide"
36+
brew "media-control"
3637

3738
# --- Utilities ---
3839
brew "fastfetch"
@@ -48,6 +49,7 @@ brew "gh"
4849
brew "git-delta"
4950
brew "lazygit"
5051
brew "lazydocker"
52+
brew "shellcheck"
5153

5254
# --- Languages & runtimes ---
5355
brew "direnv"

CLAUDE.md

Lines changed: 105 additions & 46 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ each declared tap trust as it adds it.
7070

7171
| Package | Contents |
7272
|---------|----------|
73-
| `aerospace` | [AeroSpace](https://github.com/nikitabobko/AeroSpace) tiling WM. Numeric workspaces on `alt-1``alt-9`, focus/move on `alt-hjkl`. Needs Accessibility permission; does **not** start at login |
73+
| `aerospace` | [AeroSpace](https://github.com/nikitabobko/AeroSpace) tiling WM. Numeric workspaces on `ctrl-1``ctrl-9` (**not** `alt` — that is where a Danish layout keeps `[ ] { } \`), focus/move on `alt-hjkl`. Needs Accessibility permission; does **not** start at login |
7474
| `bat` | bat config (Catppuccin Frappé theme) |
7575
| `btop` | btop Catppuccin Frappé theme (the install script seeds `color_theme = "catppuccin_frappe"` for you) |
7676
| `claude` | Claude Code settings and statusline |

aerospace/.config/aerospace/aerospace.toml

Lines changed: 88 additions & 160 deletions
Large diffs are not rendered by default.

claude/.claude/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
"type": "command",
55
"command": "bash ~/.claude/statusline.sh"
66
},
7+
"enabledPlugins": {
8+
"playwright@claude-plugins-official": true,
9+
"skill-creator@claude-plugins-official": true
10+
},
711
"effortLevel": "xhigh",
812
"tui": "fullscreen",
913
"theme": "auto",

install

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -444,20 +444,37 @@ fi
444444
# ============================================================
445445
# Post-stow seeding
446446
# ============================================================
447-
# --- sketchybar mic helper ---
447+
# --- sketchybar compiled helpers ---
448448
# Compiled, not interpreted: `swift <file>` on a trivial script measured 1.25s,
449-
# which is not pollable. The binary is written next to the stowed source, which
449+
# which is not pollable. Binaries are written next to the stowed source, which
450450
# lands in $HOME rather than in the repo because stow runs --no-folding (helpers/
451-
# is a real directory containing one symlink). mic.sh rebuilds it on demand too,
452-
# so a `git pull` that changes the source doesn't need a re-run of this script.
453-
mic_src="${HOME}/.config/sketchybar/helpers/mic.swift"
454-
if [[ -r $mic_src ]] && command -v swiftc &>/dev/null; then
455-
if swiftc -O -o "${mic_src:h}/mic" "$mic_src" 2>/dev/null; then
456-
ok "sketchybar mic helper built."
451+
# is a real directory containing symlinks). Both callers (mic.sh, system.sh)
452+
# rebuild on demand too, so a `git pull` that changes a source doesn't need a
453+
# re-run of this script.
454+
#
455+
# THREE-WAY GUARD, DELIBERATELY. An earlier version tested
456+
# `[[ -r $src ]] && command -v swiftc` in one condition, which meant a MISSING
457+
# SOURCE was indistinguishable from a missing toolchain and both exited silently
458+
# — no ok, no warn, nothing. thermal.swift was untracked in git for a while, and
459+
# a fresh clone would have produced a bar with no temperature and no explanation.
460+
# Distinguish the three outcomes so absence is loud.
461+
build_sb_helper() {
462+
local name=$1 what=$2
463+
local src="${HOME}/.config/sketchybar/helpers/${name}.swift"
464+
465+
if [[ ! -r $src ]]; then
466+
warn "sketchybar ${name} helper source missing at ${src}; ${what}"
467+
elif ! command -v swiftc &>/dev/null; then
468+
warn "swiftc not found, so the sketchybar ${name} helper was not built; ${what}"
469+
elif swiftc -O -o "${src:h}/${name}" "$src" 2>/dev/null; then
470+
ok "sketchybar ${name} helper built."
457471
else
458-
warn "sketchybar mic helper failed to build; the mic item will stay hidden."
472+
warn "sketchybar ${name} helper failed to build; ${what}"
459473
fi
460-
fi
474+
}
475+
476+
build_sb_helper mic "the mic item will stay hidden."
477+
build_sb_helper thermal "the temperature will be omitted from the system island."
461478

462479
# --- btop theme ---
463480
# btop.conf is machine-local (btop rewrites it on every exit) so it isn't tracked.

raycast/.config/raycast/scripts/reload-sketchybar.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# the same repair sketchybarrc makes at the top of itself.
2121
# USER the client resolves the running bar's mach port through it and aborts
2222
# with "sketchybar-msg: 'env USER' not set! abort.." if it is missing.
23-
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:$PATH"
23+
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:$PATH"
2424
export USER="${USER:-$(id -un)}"
2525

2626
# --reload re-executes the config in place, so the bar keeps its PID and nothing

scripts/lint-aerospace.sh

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env bash
2+
# Catches the AeroSpace failure classes that are INVISIBLE when they happen —
3+
# the sibling of scripts/lint-sketchybar.sh, same philosophy.
4+
#
5+
# AeroSpace's own validator (`reload-config --dry-run --warnings-as-errors`)
6+
# already covers syntax and unknown commands, so this script does NOT re-check
7+
# those. What it adds is the two CROSS-FILE couplings the validator cannot see,
8+
# because each half is individually valid — they are only wrong together:
9+
#
10+
# * gaps.outer.top must track SketchyBar's bar height. Get it wrong and windows
11+
# tuck under the bar or leave a band of wallpaper; nothing errors.
12+
# * the border colour AeroSpace settles on after its focus pulse must match the
13+
# resting colour in bordersrc. Get it wrong and every focus change "settles"
14+
# on the old colour; nothing errors.
15+
#
16+
# Plus the ~/.aerospace.toml ambiguity trap, and — when the AeroSpace app is
17+
# actually running — the built-in validator as a bonus.
18+
#
19+
# Run by hand or from CI. Exits non-zero on any failure. The static checks need
20+
# no binary and no running app, so they are CI-safe; the dry-run self-skips.
21+
22+
set -uo pipefail
23+
cd "$(dirname "$0")/.." || exit 1
24+
25+
TOML="aerospace/.config/aerospace/aerospace.toml"
26+
RC="sketchybar/.config/sketchybar/sketchybarrc"
27+
BORDERS="borders/.config/borders/bordersrc"
28+
CLEARANCE=5 # gaps.outer.top - bar height, by design
29+
fail=0
30+
31+
note() { printf ' %s\n' "$*"; }
32+
bad() { printf 'FAIL %s\n' "$*"; fail=1; }
33+
34+
# ---------------------------------------------------------------
35+
# 0. The files must exist where we expect them.
36+
for f in "$TOML" "$RC" "$BORDERS"; do
37+
[ -f "$f" ] || { bad "missing file: $f"; }
38+
done
39+
[ "$fail" -eq 0 ] || { printf '\nFAIL — repo layout unexpected\n'; exit 1; }
40+
41+
# ---------------------------------------------------------------
42+
# 1. Top gap must equal the SketchyBar bar height + clearance.
43+
#
44+
# aerospace.toml `gaps.outer.top` and sketchybarrc `--bar height` are coupled;
45+
# the comments in both files spell out the +5. The height= match is anchored so
46+
# it does not pick up `background.height=`.
47+
printf '\n== bar height <-> top gap coupling ==\n'
48+
height="$(grep -oE '^[[:space:]]+height=[0-9]+' "$RC" | grep -oE '[0-9]+' | head -1)"
49+
top="$(grep -E '^gaps\.outer\.top' "$TOML" | grep -oE '[0-9]+' | head -1)"
50+
if [ -z "$height" ] || [ -z "$top" ]; then
51+
bad "could not read height ('$height') or top gap ('$top')"
52+
elif [ "$top" -eq "$((height + CLEARANCE))" ]; then
53+
note "ok top=$top == height=$height + $CLEARANCE"
54+
else
55+
bad "top gap $top != bar height $height + $CLEARANCE (=$((height + CLEARANCE))) — windows will tuck under the bar or leave a band"
56+
fi
57+
58+
# ---------------------------------------------------------------
59+
# 2. Border settle colour must match bordersrc's resting active colour.
60+
#
61+
# on-focus-changed pulses to lavender then back to a mauve; that second (settle)
62+
# colour is the one that must equal bordersrc active_color. Take the LAST
63+
# active_color on the pulse line as the settle value.
64+
printf '\n== border settle colour <-> bordersrc ==\n'
65+
settle="$(grep -E 'active_color=0x' "$TOML" | grep -oE 'active_color=0x[0-9a-fA-F]+' | tail -1 | cut -d= -f2)"
66+
resting="$(grep -oE 'active_color=0x[0-9a-fA-F]+' "$BORDERS" | head -1 | cut -d= -f2)"
67+
if [ -z "$settle" ] || [ -z "$resting" ]; then
68+
bad "could not read settle ('$settle') or bordersrc active ('$resting')"
69+
elif [ "$settle" = "$resting" ]; then
70+
note "ok settle=$settle == bordersrc active=$resting"
71+
else
72+
bad "on-focus-changed settles on $settle but bordersrc rests at $resting — every focus change will settle on the wrong colour"
73+
fi
74+
75+
# ---------------------------------------------------------------
76+
# 3. No stray ~/.aerospace.toml.
77+
#
78+
# AeroSpace reads EITHER ~/.aerospace.toml OR the XDG path; having both is a
79+
# hard error and the app refuses to start. The repo config lives only at the
80+
# XDG path, so the home-dir file must not exist.
81+
printf '\n== no ambiguous ~/.aerospace.toml ==\n'
82+
if [ -e "$HOME/.aerospace.toml" ]; then
83+
bad "$HOME/.aerospace.toml exists — AeroSpace will error on the ambiguity with the XDG config"
84+
else
85+
note "ok none present"
86+
fi
87+
88+
# ---------------------------------------------------------------
89+
# 4. AeroSpace's own validator, when the app is reachable.
90+
#
91+
# reload-config is a client->server command: it validates the INSTALLED (stowed)
92+
# config and needs the app running. Skip cleanly when the binary is absent (CI)
93+
# or the server is down, so this stays a bonus rather than a false failure.
94+
printf '\n== aerospace validator ==\n'
95+
if ! command -v aerospace >/dev/null 2>&1; then
96+
note "skip aerospace not installed"
97+
elif ! aerospace list-workspaces --all >/dev/null 2>&1; then
98+
note "skip AeroSpace app not running (validator needs the server)"
99+
elif aerospace reload-config --dry-run --warnings-as-errors >/dev/null 2>&1; then
100+
note "ok reload-config --dry-run --warnings-as-errors"
101+
else
102+
bad "aerospace reload-config --dry-run --warnings-as-errors reported problems"
103+
fi
104+
105+
printf '\n'
106+
if [ "$fail" -eq 0 ]; then printf 'PASS — no silent-failure patterns found\n'
107+
else printf 'FAIL — see above\n'; fi
108+
exit "$fail"

0 commit comments

Comments
 (0)