Commit Graph

27 Commits

Author SHA1 Message Date
b27790596b Quarantine the arch_sysctl cases: they broke bt_audio's measurement
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
check_shell_coverage.sh was RED on main. bt_audio.sh is off the
allowlist at 127/127, and commit 7c14cfca -- which added
test_arch_sysctl.sh -- made kcov stop registering it entirely, so the
gate failed on a lib nothing had touched.

Bisected by measuring bt_audio.sh at three commits: a2b23521 127/127,
afe6ac3a 127/127, 7c14cfca "kcov instrumented no lines". Removing that
one test file restores 127/127 with every other new file still in the
suite (arch_cpu, arch_hardware x2, thorium x2), and arch_cpu.sh
re-measures at 57/57 alongside it.

Both plausible causes inside lib_test_core.sh were ruled out by
reverting each on its own in a throwaway worktree: `export out` and the
_t_hide farm cache each left bt_audio broken while the file was
present. Neutering the file's body while keeping the source line does
NOT reproduce it either, so the trigger is cumulative rather than one
construct, and it is not understood.

The file keeps all 23 assertions and still passes when run directly; it
just leaves the test_*.sh glob run_all.sh iterates. That costs nothing
measurable, because arch_sysctl.sh has never measured through the jail
in the first place -- it reports "instrumented no lines" on its own,
like nc_php.sh -- and it stays on the allowlist.

The header records the bisect and what to re-verify before anyone
un-quarantines it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 10:15:11 +02:00
88ba911641 Test arch_hardware.sh to 100%, fix its dead journal vacuum (90 -> 89)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
94/94 lines through run_all.sh at --min 100, exit 0. 42 assertions over
26 cases across all five functions, including both boot-loader flavours
and the no-boot-loader path.

PRODUCTION BUG, found by writing the tests and confirmed against this
host: tweak_journal's regex required a space before the unit, but
journalctl prints "305.5M in the file system" with no separator. The
size check never matched, so the vacuum was unreachable and a
multi-gigabyte journal would never have been trimmed. This is the third
instance of the same defect -- arch_perf_report.sh:36,65 carried it too
-- so the fix is identical: the separator is now optional. One case
asserts the spaced form still matches, making this a widening.

The mitigation tweak edits the boot loader, so /boot/loader/entries,
/etc/default/grub and grub.cfg are now behind overrides defaulting to
the real paths. This machine's boot configuration is never touched.

_t_hide gets a cache: it symlinks every executable on PATH (13.5k here)
and was rebuilding that farm on every call, which took the suite to 92s
and pushed a jail case past its 120s timeout. Now 77s, and arch_cpu.sh
re-measures at 57/57 so the shared change cost nothing.

The cases are split across two files because a single one was 307 lines
and file-length-cap rejected the first commit attempt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 21:46:42 +02:00
b4b6908d69 Test arch_cpu.sh to 100% and take it off the allowlist (91 -> 90)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
57/57 lines, measured through run_all.sh and confirmed a second time at
--min 100 so a shortfall would exit non-zero. check_shell_coverage.sh
passes with the exemption removed.

28 assertions over 14 cases: no cpufreq at all, every core already
tuned, the mixed case where only some cores lag, cpupower absent /
unconfigured / already configured, and all three I/O scheduler targets
(none for NVMe, mq-deadline for SATA SSD, bfq for spinning disks).

The sysfs reads and the two /etc write targets are now behind overrides
defaulting to the real paths, so the tests can present hardware this
host does not have -- a rotational disk -- without touching anything.

One assertion initially passed for the wrong reason: _t_hide keeps
$FAKE_BIN on PATH by design, so the harness's own cpupower stub still
satisfied has_cmd and the "not installed" branch never ran. Unstubbing
before hiding is now explicit, and the case fails if that is lost.

This also settles arch_sysctl.sh's diagnosis: it uses this same
harness, so the harness is not why it cannot be measured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 21:14:57 +02:00
7c14cfca61 Test arch_sysctl.sh and open the optimize_arch_desktop lib family
23 assertions over 8 cases covering both tweak_vm_sysctl and
tweak_network_sysctl: the skip path, the write-when-the-drop-in-is-
missing path that a naive "nothing to do" check gets wrong, the
value-differs path, an unreadable-sysctl path, and the tcp_bbr guard.

arch_desktop_harness.sh is the third family harness, supplying the
globals and apply_tweak that live in optimize_arch_desktop.sh rather
than in any lib. arch_cpu.sh and arch_hardware.sh will reuse it.

SYSCTL_DROPIN_DIR defaults to the real /etc/sysctl.d, matching the
seam ubuntu_perf_more.sh and arch_perf_fixes.sh already carry; the
suite runs un-jailed in ci_mirror.sh and CI, so a bind mount would
protect nothing. Both real drop-ins keep their Mar 2 mtimes.

_t_run lands in lib_test_core.sh because `out="$(func)"` forks a
subshell and kcov never registers a lib whose first execution happens
inside one -- measured as 0 lines via $( ) against 54 lines via a
direct call.

arch_sysctl.sh STAYS on the coverage allowlist: it still reports "kcov
instrumented no lines" through the jail, while ubuntu_perf_more.sh
measures 32/32 in the same worktree and the same invocation. The tests
are real and green; the coverage number is not yet obtainable, and the
exemption may only come off once it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 20:54:47 +02:00
afe6ac3a33 Reformat three fixes libs with shfmt ahead of coverage work
shfmt runs on every staged shell file and rewrites drifted libs, which
moves coverage denominators and invalidates measurements taken minutes
earlier. Landing the reformat first makes every number measured from
here the number that survives pre-commit.

thorium_repairs.sh converts from 2-space indent to tabs; the other two
only lose runs of blank lines. `git diff -w` shows no token changed,
shellcheck is clean, and the 20-file suite stays green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 20:22:23 +02:00
0c92a00f0f Test arch_perf_fixes.sh to 93.48% and fix the journal regex a third time
Eighth of the 16 untested fixes/lib libs. 22 assertions; 43/46 = 93.48%. The
lib STAYS on the allowlist -- see the artifact below.

Third instance of the same dead branch. Line 16 matched on
`([0-9]+\.?[0-9]*)\ G`, a required space before the unit, while journalctl
prints "4.2G" with no separator, so the vacuum could never run. Fixed to an
optional space, as in arch_perf_report.sh.

This one differs from that earlier fix and the difference was confirmed with
the user before landing: arch_perf_report.sh's vacuum sits behind
--apply-safe-fixes, but THIS one runs whenever fix_arch_performance.sh runs.
So the fix makes an unflagged destructive action live -- the script will now
really vacuum the journal to 300M once it passes a gigabyte. That is the
function's evident intent, since its next act is to write a permanent 300M
cap, and the user approved it explicitly.

Three absolute paths moved behind overrides defaulting to the real ones:
JOURNALD_CONF_DIR, SYSTEMD_UNIT_DIR (fix_media_organizer writes
/etc/systemd/system/media-organizer.service) and ORGANIZE_SCRIPT_CANDIDATES.
Verified afterwards that the real unit file still carries its 2026-07-25
mtime and the journald drop-in its 2026-03-02 one, i.e. a bare un-jailed
run_all.sh touched neither.

The three uncovered lines (68-70) are the element lines of a multi-line array
literal. `PS4` + `set -x` on a minimal case shows bash reporting the whole
assignment at its OPENING line and never emitting the elements or the closing
paren, so they are data, not statements -- a fourth flavour of defect (a),
after multi-line quotes, `} |` and the backslash-continued assignment. The
array is provably used: the loop on the next line iterates it and the
media-organizer assertions pass.

Worth fixing, unlike the bt_adapter one-liner: the form appears in three of
the remaining untested libs (thorium_repairs.sh has four) and in nine
allowlisted libs repo-wide. That lands as its own commit next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:34:12 +02:00
99a9804d75 Test ubuntu_perf_more.sh to 100%: allowlist 93 -> 92
Seventh of the 16 untested fixes/lib libs and the first of the ubuntu-perf
pair, which gets its own harness here. 24 assertions; 32/32 = 100.00%
through run_all.sh on the first measurement, confirmed at --min 100 (exit 0),
then cleared with the real gate and no exemption.

Two absolute paths had to move behind env overrides, defaulting to the real
ones: fix_journal writes /etc/systemd/journald.conf.d/size-limit.conf and
run_undo globs /root for undo scripts. Binding those in the jail was rejected
for the same reason as bt_pairing.sh's udev rule -- run_all.sh runs UN-jailed
in ci_mirror.sh and in CI, so a bind mount protects the coverage run and
nothing else. JOURNALD_CONF_DIR and UNDO_DIR follow the UDEV_RULES_DIR
precedent.

Proof the suite is safe where it runs un-jailed: after a bare run_all.sh the
real /etc/systemd/journald.conf.d/size-limit.conf still carries its 2026-03-02
mtime, a genuine artifact of a past production run, and no
undo_ubuntu_performance_*.sh exists in /root.

Worth noting for the rest of the family: run_undo ends in `exit`, so its cases
run inside an explicit subshell. A command substitution already provides one,
so the exit ends only that subshell rather than the whole test file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:25:21 +02:00
fa7b05da74 Test arch_perf_probes.sh to 100%: allowlist 93 -> 92
Sixth of the 16 untested fixes/lib libs, and the first with no dead branch to
fix -- the whole job was test-writing. 43 assertions across two files;
93/93 = 100.00% through run_all.sh, twice, the second at --min 100 (exit 0),
then cleared with the real gate confirming and no exemption.

The one genuine gap was check_memory_pressure's high-swap branch (lines
158-159), which fires only when swap is heavily used WHILE RAM is still
available -- a state this host does not happen to be in. The lib reads the
absolute /proc/meminfo, but every read goes through `awk`, so stubbing awk
supplies controlled figures without touching /proc: the stub answers on the
pattern being matched and execs the real awk for anything else. Both
directions are covered, including RAM genuinely exhausted, which must NOT be
flagged because it is not the stutter signature.

Everything else here is branch coverage of probes whose inputs are external
commands: failed systemd units, ACPI error counts, a CPU-hungry Xorg,
governors all-performance vs one-powersave vs unsupported, thermal
throttling excerpts, the TLP/power-profiles-daemon conflict in all three
combinations, and storage health with and without fstrim/smartctl.

lib_test_core.sh gains _t_stub_cat. "Print this fixture file" is by far the
commonest stub shape, and writing it inline forces every call site to embed
a literal ${LIB_TEST_DEV} that must not expand at write time -- SC2016 on
every use. Passing the fixture NAME as data removes the class outright, and
two earlier bluetooth test files are simplified onto it. All 21 test files
and three harnesses now lint clean with no suppression anywhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:04:50 +02:00
ffcf99f102 Test arch_perf_report.sh to 98.33% and fix its dead journal-size check
Fifth of the 16 untested fixes/lib libs and the first of the arch-performance
group, which gets its own harness here. 38 assertions across two files. The
lib STAYS on the allowlist at 59/60 = 98.33%, measured twice.

The second dead branch this campaign has found, fixed here. Lines 36 and 65
matched on:

    ([0-9]+\.?[0-9]*)\ (G|M)

a REQUIRED space before the unit. journalctl actually prints "Archived and
active journals take up 305.5M in the file system." with no separator --
verified against the real binary on this machine. So check_journal_size could
never report a large journal and apply_safe_fixes could never vacuum one:
both were unreachable. The space is now optional, and tests cover the spaced
and unspaced forms so neither can rot.

NOTE: this makes a previously-dead DESTRUCTIVE action live. With
--apply-safe-fixes and a journal over a gigabyte, the entry script will now
really run `journalctl --vacuum-size=300M`. That is the flag's documented
purpose and it stays behind the flag, but it has never actually executed
before today.

Line 20, the one uncovered line, is `} >>"$REPORT_FILE"` -- the closing brace
of a redirected group. A minimal probe writes both inner lines to the file
while bash attributes no statement to the brace, and the report assertions
independently prove the group runs, since they read the text it writes. Same
defect (a) class as bt_audio's `} |`.

That form now caps four libs: bt_audio.sh, arch_perf_report.sh, and
arch_perf_probes.sh and nvidia_config.sh which are still untested. So the
continuation_lines() fix is worth landing after all -- as its own commit,
next, so a denominator change can be reverted without taking these tests
with it.

_t_hide and _t_full_path move from bt_harness.sh into lib_test_core.sh: they
are generic PATH plumbing and the arch harness needs them for the
"nvidia-smi is not installed" branch. All twelve existing suites re-run green
after the move.

One trap worth recording: apply_safe_fixes appends to the ACTIONS array, so
it must be called directly rather than inside "$(...)" -- a command
substitution runs it in a subshell where the appends die with it, and the
assertions read an empty array.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:06:57 +02:00
a5b6663c02 Test bt_audio.sh to 96.97% and fix its wpctl health check
Fourth of the 16 untested fixes/lib libs, the largest at 214 lines, and the
last of the bluetooth family. 47 assertions across three files. The lib
STAYS on the allowlist: 128/132 = 96.97%, twice with the same line set.

The bug the tests found, fixed here. Line 21 read:

    if timeout 3 _run_as_user wpctl status &>/dev/null; then

`timeout` is a binary and `_run_as_user` is a shell function that is never
exported, so this always failed with rc=127 -- "timeout: failed to run
command '_run_as_user'". check_pipewire_health therefore reported PipeWire
hung on EVERY run and restarted a healthy audio stack every time
fix_bluetooth.sh ran. The healthy branch was unreachable, which is why the
test for it could not be written without first noticing. Now
`_run_as_user timeout 3 wpctl status`: same bound, inside the sudo'd command.

Why 96.97% and not 100%. An earlier run of this suite read 99/99 = 100.00%,
and that number was wrong -- it measured the file before pre-commit's
`shfmt -w` reformatted it. shfmt rewrites the `{ ...; } \` + `| bluetoothctl`
one-liners into multi-line blocks ending in `} |`, which raises the
denominator 99 -> 132. Confirmed unavoidable by running shfmt against HEAD's
own copy: it rewrites there too, so reverting the reformat is not stable.

The four uncovered lines (90, 113, 137, 157) are each that `} |` -- the pipe
operator joining a block to bluetoothctl, not a statement. They do execute:
a `{ echo a; echo b; } | consumer` block writes every marker to a file even
though bash's xtrace never reports the left-hand side, because a pipe's LHS
runs in a subshell whose xtrace is not captured. kcov instruments them and
cannot attribute them; defect (a) in docs/kcov-under-report.md, third flavour.

Scope, measured: the continuation-pipe form appears ONLY in bt_adapter.sh
(pre-format) and bt_audio.sh (post-shfmt). None of the remaining twelve
untested libs carries either, so this does not block the campaign and the
detector fix stays off the critical path.

Also worth 90.91% -> 96.97% on its own: the bluetoothctl stubs now drain
stdin before exiting. Real bluetoothctl consumes its whole input; a stub
that read one line and exited closed the pipe early, so the writing block
took SIGPIPE partway through and its later lines never ran.

NOTE for whoever edits this file next: bt_audio.sh is now exactly 250 lines,
at the cap. The next change to it needs a split, not a comment trim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 17:52:24 +02:00
434cb7587e Test bt_adapter.sh to 98.55%; line 97 is unreachable by construction
Third of the 16 untested fixes/lib libs, and the first that does NOT come
off the allowlist. 68/69 = 98.55%, up from 45/69. The remaining line is
not a missing test:

  PS4='+TRACE:${BASH_SOURCE##*/}:${LINENO} ' bash -x

shows bash attributing the whole multi-line dbus-send assignment in
_services_resolved to line 100, and never emitting line 97 at all. Line 97
is the OPENING line of a backslash-continued statement; kcov instruments
it, bash never attributes a statement to it. That is defect (a) in
docs/kcov-under-report.md -- a wrong denominator -- and no test can move it.

continuation_lines() in shell_coverage_lines.py already excludes such lines,
but only detects them by tracking QUOTE state, so a line ending in an
unquoted backslash slips through. Fixing that is deliberately left out of
this commit: it changes the denominator for every measured lib, and if it
regresses one of the libs now gated with no exemption, reverting a mixed
commit would throw away these tests too.

Scope of the problem, measured rather than guessed -- backslash
continuations are confined to the bluetooth family: bt_audio.sh 10,
bt_adapter.sh 7, bt_pairing.sh 3, pacman_hook_stall_summary.sh 2, and ZERO
in the other 12 remaining libs. So this caps bt_audio.sh next and then stops
mattering.

Also replaces the harness's _t_only_path with _t_hide. _t_only_path dropped
PATH to $FAKE_BIN alone, which hid grep, head and sort as well as the tool
under test -- so the "usbreset is not installed" case died in a cascade of
"command not found" and passed for the wrong reason instead of exercising
the fallback. _t_hide rebuilds PATH from symlinks to every real executable
except the named ones; verified directly that command -v usbreset is then
empty while grep and head still resolve.

bt_report.sh (37/37) and bt_pairing.sh (65/65) re-measured at --min 100,
both exit 0, so neither cleared lib regressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 17:10:15 +02:00
0e9cc0ce95 Test bt_pairing.sh to 100%: allowlist 97 -> 96
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Second of the 16 untested fixes/lib libs. 65/65 = 100.00% through
run_all.sh, twice, the second at --min 100 (exit 0), then cleared from the
allowlist and confirmed by the real gate with no exemption.

Two findings worth keeping:

- udevadm was NOT in the harness's stub list, so `udevadm control
  --reload-rules` was reaching the REAL binary during the suite. A
  prepended stub dir cannot hide a tool the host actually has. Caught by
  asserting on the recorded call rather than by reading the code, which is
  the argument for asserting on calls at all.

- _disable_usb_autosuspend writes /etc/udev/rules.d/50-bluetooth-no-
  autosuspend.rules. Binding /etc in the jail would have covered it, and
  was rejected: run_all.sh runs UN-jailed in ci_mirror.sh and in the
  shell-tests workflow, as its own header says, so a bind mount protects
  the coverage run and leaves every bare run writing to a real root-owned
  file. Instead the lib gained a UDEV_RULES_DIR override, defaulting to the
  real path -- the same env-override seam pacman_hook_stall_harness.sh uses
  for PACMAN_LOG/CACHE_DIR/PACMAN_LOCK.

Proof the suite is safe where it runs un-jailed: after a bare run_all.sh,
the real rule file still has its 2026-02-21 mtime (it is a genuine artifact
of a production run six months ago) and the probed power/control values are
unchanged. The jail cannot give that check.

fix_usb_autosuspend globs the real /sys/bus/usb/devices/*/ with no override,
so its branches are driven through stubbed lsusb output: ffff:ffff matches
nothing on any machine and reaches the not-found arm, while 046d:c077
(control=on) and 046d:0825 (control=auto) are read-only probes, each guarded
so the case self-skips with a note if the host state changes rather than
silently asserting nothing.

Split across two test files to stay under the 250-line cap; run_all.sh globs
test_*.sh, so splitting costs nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:47:40 +02:00
4cfc3fdde1 Test bt_report.sh to 100%: allowlist 98 -> 97
First of the 16 untested fixes/lib libs. Measured 37/37 = 100.00% through
lib/tests/run_all.sh, twice, the second run at --min 100 (exit 0), then
removed from the allowlist and confirmed with the real gate and no
exemption.

Adds two reusable pieces rather than a per-family harness clone:

- lib_test_core.sh -- PASS/FAIL tally, _t_eq/_t_contains/_t_lacks, a
  mktemp -d sandbox wiped on EXIT, a FAKE_BIN stub dir on PATH, and
  _t_stub/_t_unstub. _t_unstub runs `hash -r`: deleting a stub file alone
  does not drop bash's cached executable path, so the real binary would
  never be reached again.
- bt_harness.sh -- sources the core, sources the real scripts/lib/common.sh
  for the production log_*/has_cmd helpers (verified inert at source time),
  and redefines _btctl and apply_fix verbatim from fix_bluetooth.sh, since
  those live in the entry script rather than in any lib. Same trade-off
  pacman_hook_stall_harness.sh already makes with log_size.

pacman_hook_stall_harness.sh is deliberately left alone: its six libs are
already off the allowlist, i.e. gated at 100% with no exemption, so
refactoring it onto the new core would risk banked work for no gain. jscpd
reads 1.6% against the hook's own flags, and **/lib/tests/** is excluded
from that scan anyway, so the duplication budget is not at stake.

_t_only_path exists because every tool these libs call (usbreset, pactl,
lsusb, ...) genuinely exists on an Arch desktop: a prepended stub dir
cannot exercise a "not installed" branch, so those cases drop PATH to
FAKE_BIN alone.

The three coverage runs were made in a detached worktree: a second session
was writing to this repo concurrently, and the jail aborts on any
working-tree delta during a run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:33:33 +02:00
347222b921 Add fixes/lib run_all.sh; the previous handoff was wrong about that directory
The handoff recorded fixes/lib as having "no harness at all". It already had
8 test files and 2 harnesses. What was missing is run_all.sh -- the subject
is_covered() actually builds, and the only thing the gate measures through.
All 8 test files pass through it.

No jail_args beside it, deliberately: the harness confines every write to its
own `mktemp -d` and intercepts external commands with a PATH stub dir, and
grep finds no absolute write to /etc, /var or /usr in any of the tests. So it
runs bare, like the transcribe suite.

First measurement through it: pacman_hook_stall_usage.sh is 1/1 = 100.00%.
That denominator is honest, not broken -- the lib is a single usage() whose
body is one `cat <<EOF` heredoc plus `exit 0`, and heredoc content is data
rather than statements.

16 of the 22 libs there still have no tests at all. That is now the largest
block of real test-writing work left in the campaign.

Also rewrites NEXT_SESSION_PROMPT.md, whose central claim ("the instrument is
the bottleneck, not the tests") stopped being true this session. The new one
leads with re-measuring the 103 allowlisted libs on the fixed instrument
before writing any new test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:40:31 +02:00
015eb48714 diagnose_pacman_hook_stall.sh: split 493 -> 249 + 6 libs, 100% coverage
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Confirmed live (not dead code) via the dead-code check-in before any code
was touched -- the last two candidates this pattern was tried on were both
archived instead. Kept run_one/cleanup/log_size/main together in the entry
script: a prior session (2527fb3) already proved splitting run_one out
produces a real SC2153/SC2034 pair since PACMAN_PID/LAST_ELAPSED cross the
EXIT trap, not a suppressible false positive.

Fixed a real bug found while writing tests: print_summary's inline awk had
a dead `NR == 0` guard (printf on an empty array still emits one blank
line), reachable via `-n 0`. Refactored mem_available_mb, descendant_pids/
kill_tree, capture_stall's two output blocks, and print_summary's median
calc after confirming empirically (bash -x tracing) that kcov's
xtrace-based coverage can never see a `done <redirect>`, `} >redirect`, or
inline-awk line -- user chose to fix the code over the coverage tool.
descendant_pids specifically needed an explicit-fd process substitution
(not a pipe) to keep its array mutation in the current shell.

Made PACMAN_BIN/PACMAN_LOG/CACHE_DIR/PACMAN_LOCK/LOAD_FLOOR_MB/
LOAD_MIN_FREE_MB env-overridable for testing. Caught and fixed, before it
ran unbounded, a --with-load test that would have dd'd 13-20+ GB into
/dev/shm using this box's real available memory.

56 lib-level + 13 entry-level PATH-shim/subprocess assertions, 0 failed.
The entry test itself grew past 250 lines while adding coverage and had to
be split into a shared harness plus two test files, mid-session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-18 22:33:57 +02:00
76e73830f6 shell: split clean_audio, enforce_vbox_hosts and migrate_hosts_guard
clean_audio.sh          419 -> 230 + 204
  enforce_vbox_hosts.sh   443 -> 220 + 236
  migrate_hosts_guard.sh  444 -> 167 + 162 + 141

migrate_hosts_guard needed two libraries: with all ten work functions moved
the single lib was 295 lines, so migrate and rollback were separated.

The extractor's brace matcher required index > start and so silently skipped
one-line `warn() { ...; }` definitions. Fixed, and regression-checked against
the committed fresh-install/main.sh split byte-for-byte.

The harness earned its keep on a fourth file: adding `export` to libre_translate
to satisfy SC2034 changed two function bodies, and verify_shell_split.sh caught
what lint had accepted. Those exports were reverted, and libre_translate.sh is
parked - what remains after its clean seams both assigns and reads the same
globals, so getting it under the cap needs parse_args restructured rather than
relocated, which is a refactor rather than a verbatim move.

That cost is now written down: a lib must contain the readers of every global
it assigns. The precedent at install_pacman_wrapper.sh:29 is about
definitions-only libs, not about assignments as such, and the pre-commit hook
runs shellcheck with no -x, so a source= directive will not make it follow the
link. This is the shell analogue of the monkeypatch-target trap.

Verified: all 8, 13 and 19 functions verbatim; all four libraries source with
no side effects; shellcheck passes standalone on every file with no
suppression; bash -n passes on all seven. None of the three was executed -
they drive ffmpeg over real media, mutate VirtualBox guest disks, and rewrite
systemd units.

Over-cap files 29 -> 26.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:27:57 +02:00
efd7f022fd fixes/utils: split nvidia_troubleshoot 336 -> 169+181, passwordless 374 -> 195+193
Two single-use scripts with no deployed copy, so the entry+lib shape is
safe. Each keeps the ordering, the safety checks and the user-facing output;
the configuration writers move to a library beside it.

Both scripts write `set -e # Exit on any error`, so extract_shell_functions.py
now matches the directive rather than the whole line. Regression-checked by
re-deriving the committed fresh-install/main.sh split byte-for-byte.

Verified: all 6 and all 9 top-level functions verbatim via the shfmt
harness; both libraries source with no side effects and define every
function moved into them; bash -n passes on all four.

Neither script was run. nvidia_troubleshoot.sh writes /etc/X11 config and
installs pyroveil; setup_passwordless_system.sh writes sudoers and lightdm
autologin, which is what this machine's login depends on.

Over-cap files 31 -> 29.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:18:06 +02:00
63650fbc45 Split offline docs and arch optimiser; defer libre_translate
setup_offline_docs.sh 713 -> 121 across six libs, carved one language at
a time since the first cut left offline_cpp at 545. optimize_arch_
desktop.sh 623 -> 118.

Two recurring traps again: a script with no SCRIPT_DIR at all, and libs
that chained rather than being sourced from the entry. Both now checked
for routinely.

libre_translate.sh is reverted and stays at 488. Exporting GENERATED to
fix one SC2034 revealed eighteen more - when a fix uncovers more of the
same, the seam is imaginary, not the fix wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 20:09:54 +02:00
194b006453 Split fix_bluetooth.sh 720 -> 186; defer steam_compatibility.sh
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Four libs written in one pass rather than four sequential splits, which
is what stops the libs chaining - the entry gets one source block naming
all four and no lib sources a sibling.

steam_compatibility.sh is reverted and stays at 663. Its CACHE_MAP
associative array is declared and filled in what would be the ProtonDB
lib but read by main, so that lib fails SC2034 linted alone. Third file
deferred for the same reason: shared mutable state means the seam is not
there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:17:05 +02:00
2527fb37e6 Split three large linux_configuration scripts, defer a fourth
organize_downloads.sh 513 -> 72, setup_kcd2_dice_solver.sh 497 -> 112,
fix_ubuntu_performance.sh 494 -> 150. First 490+ line scripts done; all
three needed two or three seams.

Repeated splitting chained the libs - each sourced the next, because the
tool inserts its source line relative to the file it just wrote.
Flattened so the entry sources all three and no lib sources a sibling.

diagnose_pacman_hook_stall.sh is reverted and stays at 493. Its
transaction runner assigns LAST_ELAPSED that the caller reads and reads
PACMAN_BIN that the caller defines, so linted alone it is SC2034 plus
SC2153. The pre-commit hook lints each lib in isolation and fails, while
shellcheck -x on the entry passes - suppressing that would be papering
over a seam that does not exist.

organize_downloads.sh is the ExecStart of media-organizer.service; the
unit still resolves and the script still parses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 19:06:16 +02:00
7e967de79b Split seven more linux_configuration scripts
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
10-recon.sh, install_exercism.sh, fix_arch_performance.sh,
format_sd_card.sh, setup_dot_resolver.sh, fix_unity.sh and
20-dump-stock.sh, all now under the cap.

Splitting a file twice is the risky move: the second cut on
20-dump-stock.sh dropped the first cut's source line, leaving
verify_dump called but never defined, and the repair then landed the
line inside the lib instead of the entry script. Both fixed - the entry
sources both libs and no lib sources another. Grep the source lines
after any double split.

fix_unity.sh's desktop_dir is read across the seam; renamed to
DESKTOP_DIR to match the file's own convention for globals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:56:05 +02:00
58fdc6c9de Split five more linux_configuration single_use scripts
install_unreal_mcp.sh 395 -> 155, diagnose_arch_performance.sh 377 ->
123 (two cuts), setup_local_llm.sh 370 -> 163, repo_to_study.sh 365 ->
160, setup_dwm.sh 359 -> 147.

The SCRIPT_DIR fix from the last batch held on all five. Fixed a second
unguarded cd, this time before the study-material generator ran - the
same latent bug class as build_website, surfaced because a moved
function gets linted in isolation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:51:28 +02:00
1a75ed5bf5 Split three more linux_configuration feature and fix scripts
setup_syncyomi.sh 408 -> 138 (two cuts), setup_personal_website.sh
396 -> 192, fix_thorium.sh 377 -> 208.

All three already declared SCRIPT_DIR and the splitter added a second
one, which aborts at runtime with "readonly variable" while bash -n and
shellcheck stay clean. The stubbed run caught it; the splitter now
reuses an existing declaration.

Fixed SC2164 in build_website rather than suppressing it: a bare
cd "$WEBSITE_SRC" inside a subshell, with pnpm install and pnpm run
build after it. A failed cd would have built in the caller's directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:48:01 +02:00
b83ce5d3c7 Fail the build on any shellcheck finding; fix the 222 the old gate hid
Some checks failed
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
billsplit CI / quality (push) Has been cancelled
reverse-survivors CI / check (push) Has been cancelled
The shellcheck hook ran with --severity=warning, so info- and style-level
findings never failed a commit. 222 had accumulated across 94 of 244 shell
files. They were not all style noise -- six were bugs:

  - Five scripts in single_use/misc/testsAndMisc-bash/ sourced
    $SCRIPT_DIR/../../lib/common.sh, one directory too shallow. Nothing exists
    at that path, so all five failed at runtime.
  - setup_personal_website.sh and setup_kcd2_dice_solver.sh called usage()
    (which ends in `exit 0`) and then `exit 1` for an unknown command. The
    exit 1 was unreachable, so a typo'd subcommand reported SUCCESS.
  - Six i3blocks scripts had a `# shellcheck source=` directive missing the
    scripts/periodic_background/ path segment. The stale path had silently
    disabled source-following for all of them.
  - diagnose_arch_performance.sh word-split a newline-joined file list; when
    the find came back empty, awk got no file operands, fell through to
    reading stdin, and the diagnostic hung.
  - lib/common.sh's handle_arg_help_or_unknown ended in an unreachable
    `return 0` while documenting "Returns: 0 if handled" -- a contract no code
    path could satisfy.
  - deploy.sh's SCRIPT_DIR is reassigned by config.sh, which it sources at top
    level, and config.sh can repoint it at the on-device path
    /data/local/tmp/focus_mode. Renamed to DEPLOY_DIR.

The SC1091 bulk (81 findings) was resolved rather than silenced: .shellcheckrc
sets external-sources + source-path=SCRIPTDIR so sourced files are actually
followed and analysed. That is what surfaced the i3blocks and deploy.sh bugs.

17 `# shellcheck disable=` directives remain, each with a written reason, and
only where the finding is provably wrong: injection-test payloads that must not
expand, literal source text being grepped for, POSIX sh scripts on the phone
where arrays do not exist, and source paths that only exist at runtime.
Everything else was fixed in code.

Verified in a CLEAN worktree, not just my own tree. config.sh sources the
gitignored config_secrets.sh, which exists locally but not in a fresh checkout;
a working-tree-only check passed while ci_mirror.sh -- which lints HEAD in a
clean worktree, exactly as CI does -- failed on it.

The gate is verified to bite: a file whose only finding is SC2016 (info) fails
the hook, while the old --severity=warning gate exits 0 on it.

third_party/ is excluded -- vendored upstream, matching the prettier hook.

Not fixed, reported: check_polling_antipatterns.sh carries a pre-existing
SC2034 disable that hides a real bug -- its `patterns` array is never used, so
the checker detects nothing. Confirmed unchanged by this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:49:11 +02:00
79829ec8a2 Diagnose the pacman first-hook stall; fix the drift that hid a stale wrapper
pacman intermittently freezes at its FIRST PreTransaction hook - 5 abandoned
transactions since 2026-07-29, stalls of 4 minutes to overnight, always fine on
retry. This investigates it and repairs the real defects found on the way.

The stall is NOT fixed, and deliberately so: no verified defect was found, so
nothing pacman-side was changed.

  - Not guard-lib. /etc/guard-lib/targets was empty, so that hook script
    provably exits in milliseconds. The failures share POSITION (first hook of
    the transaction), not identity: one stall hit the hosts hook back when that
    one ran first.
  - Not reproducible on demand. 65 real transactions (40 idle, 25 under 2.6 GB
    of memory pressure) produced zero stalls. Against the historical 4-in-56
    rate that is ~0.9% likely, so the trigger is conditional on something a
    synthetic load does not recreate. Memory pressure is untested at the load
    that actually occurred - not refuted.

So diagnose_pacman_hook_stall.sh drives real transactions with a watchdog, and
its --watch mode passively captures /proc/<pid>/{stack,wchan,syscall}, the
process tree, meminfo and PSI from the next REAL stall. No pacman-side fix
should ship before that dump exists.

Three real defects were found and fixed:

1. The deployed pacman wrapper was 19916 B against a 27510 B source, never
   sourcing the shared pacman_lock_lib.sh that makepkg_wrapper already used.
   It hid because check_pacman_wrapper() only tested that files EXIST. Both
   installers now write a drift manifest covering source AND installed hashes;
   the checker replays it with sha256sum -c and fails closed when it cannot
   verify. Manifest paths are absolute - relative ones resolve to nothing under
   systemd's cwd=/ and would have looped reinstalling forever.

2. The guard-lib migration (66c4698) was never finished here: guard-lib was
   installed with zero registered targets while the legacy hosts-guard layer did
   all the work, so every transaction ran two PreTransaction hooks and the first
   one did nothing. migrate_hosts_guard_to_guard_lib.sh registers hosts,
   nsswitch and resolved, retires the legacy hooks and units, and has a
   --rollback that was actually exercised. Plugins install to a stable path
   because guardctl records absolute plugin paths. Side effect: one hook pair
   instead of two, transactions ~5-6s -> ~2-3s.

3. "One heavy job at a time" was a rule a human had to remember. heavy_job_lock.sh
   makes the overlap impossible: one system-wide flock shared between root and
   the user (a per-user lock would give root's pacman and the user's builds
   separate locks and no exclusion at all). Wired into pacman TRANSACTIONS only
   - read-only queries stay instant - plus makepkg_capped and the coverage hook.
   Always fails open.

Two bugs surfaced only by running --rollback, both of which briefly left
/etc/hosts with no immutable flag: fg_uninstall clears +i on its way out while
legacy watchers only fire on PathModified, and `((i > 20)) && break` left the
loop body's status at 1, which under set -e aborted the rollback silently - but
only when the loop actually ran, so --dry-run looked healthy.

Not migrated: the shutdown-schedule guard (/etc/shutdown-schedule.conf does not
exist here). Not wired: Flutter/Gradle builds, which have no single entry point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 15:15:34 +02:00
071963b92b Fix: stop the Flutter SDK reading your own repo as itself inside git hooks
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
`git commit`/`git push` export GIT_DIR to every hook they run, and the
flutter tool shells out to git to identify its own SDK. `git -C
"$FLUTTER_ROOT" rev-parse HEAD` in bin/internal/shared.sh does not beat an
inherited GIT_DIR -- `-C` only changes directory, GIT_DIR overrides
repository discovery outright:

    $ git -C /opt/flutter rev-parse HEAD
    058e0af2c2...                                  <- Flutter's HEAD
    $ GIT_DIR=~/diet-guard/.git git -C /opt/flutter rev-parse HEAD
    84bd550331...                                  <- diet-guard's HEAD

So inside a hook, flutter reports the project as the SDK:

    Flutter 3.47.0-0.1.pre • channel main • https://github.com/kuhyx/diet-guard

pub then resolves the SDK as 0.0.0-unknown and every version constraint
becomes unsatisfiable. flutter also writes that into its own version cache,
so the wrong answer outlives the hook and follows the next caller into an
unrelated repository -- which is why the identical command passes when you
re-run it by hand and fails again on the next push.

diet-guard's pre-push gate hit this today. Clearing the variables per call in
each hook works, and screen-locker and diet-guard now do, but only for as
long as every future hook author remembers -- deployment hygiene, not a fix.
This installs a wrapper ahead of /usr/bin on PATH that strips them for every
caller in every repo, covering both flutter and dart. It removes its own
directory from PATH before exec so the Flutter entry point's internal `which
flutter` cannot loop back into it, and it verifies itself by running flutter
against a throwaway poisoned GIT_DIR and failing unless the real SDK comes
back.

Not patching bin/internal/shared.sh: that file belongs to the flutter-bin
package, so the next update would silently restore the bug. Upstream already
unsets these variables in content_aware_hash.sh and update_engine_version.sh,
just not in the version path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 15:39:33 +02:00
42a66a1419 refactor(linux_configuration/scripts): split all scripts into single_use/ and periodic_background/
- Move all linux_configuration scripts into two semantic categories:
  - single_use/: scripts run once manually (fresh install, fixes, setup)
  - periodic_background/: scripts run by systemd timers or daemons
- Preserve existing subdirectory structure within each category
- Fix lib/common.sh source paths for new directory depths
- Fix CONFIG_DIR depth in setup_periodic_system.sh and check_and_enable_services.sh
- Update all references in tests, fresh-install/main.sh, nix modules, and docs
- Fix check_polling_antipatterns.sh false positives (||, regex |, case patterns, jq strings)
- Fix pre-existing mypy exclusion path and type annotations for moved tools/ directory
- Rewrite check_polling_antipatterns.sh using awk (no bash regex loops); add require_serial: true
2026-05-15 00:32:35 +02:00