Probing found a blocker the trace structurally cannot see: a script that some OTHER script copies elsewhere and runs from there cannot take an entry+lib/ shape, because the deployed entry's SCRIPT_DIR has no lib/ and dies on its first source under set -e. The trace stays green because it runs the repo copy. install_leechblock.sh is blocked on exactly this, and it is live, not theoretical: install_pacman_wrapper.sh cp's it to /usr/local/share/digital_wellbeing/, pacman_wrapper.sh:831 PREFERS that copy, and auto_install_leechblock runs on every pacman invocation. It is otherwise the best-conditioned target left (exit 0, 93 files, all hashes stable), which is exactly why it needed the check rather than the trace. setup_thorium_startup.sh is mechanically splittable — exit 0, all 8 steps covered, no deployed copy — but should not be split, because what it configures is dead. Thorium was removed in favour of chromium/librewolf; /usr/local/bin/thorium-browser is now a leftover symlink to browser-preexec-wrapper, which cannot resolve /usr/bin/thorium-browser and so treats the URL as the binary. The enabled thorium-fitatu-startup.service has been failing at every boot while reporting status=0/SUCCESS: thorium-browser: line 33: /https://www.fitatu.com/app/planner: No such file Also recorded: process spawning escapes --prefix the same way pkill does. setup_thorium_startup.sh launches a browser mid-run; check ps after tracing anything that starts a program. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
10 KiB
Next session: install_leechblock.sh is BLOCKED — read this first
Paste this whole file into a fresh Claude session opened in
~/testsAndMisc. It is self-contained. Do not go looking for the previous session's context.
Do not split install_leechblock.sh
The previous brief pointed here. It was wrong, and the reason is worth reading before picking any target.
install_leechblock.sh traces beautifully — exit 0, 93 written files, all 93
hashes byte-identical across runs with --stub pkill. By every signal the
harness gives you, it is the best-conditioned target left. It is still
unsplittable in an entry+lib/ shape, because it is DEPLOYED as a single
file:
install_pacman_wrapper.shcps it to/usr/local/share/digital_wellbeing/install_leechblock.sh(confirmed present on this machine, byte-identical to the repo copy, nolib/).pacman_wrapper.sh:831prefers that deployed copy, andauto_install_leechblock "$@"runs on every pacman invocation.check_and_enable_services.sh:46also references it, andperiodic-system-maintenance.timeris active (hourly).
An entry whose SCRIPT_DIR has no lib/ dies on its first source under
set -e. The trace cannot see this — it runs the repo copy, where lib/
exists. This is the same blocker as block_compulsive_opening.sh.
Splitting it is possible but is a two-file job: teach the pacman installer to
deploy lib/ too, which changes that file's 16-entry trace manifest and forces
a re-baseline of a file that was verified last session. Decide that deliberately
if you take it on; don't stumble into it.
Generalise the lesson: before designing seams, run
grep -rn '<target-basename>' linux_configuration/ --include='*.sh' \
| grep -v '<the target itself>:'
A target invoked as $CONFIG_DIR/scripts/... (from the repo) is safe; one that
is cp'd somewhere and run from there is not.
What landed in the last session (do not redo)
Two splits, both committed and pushed, both proven by an empty before/after trace diff:
install_usage_monitoring.sh290 → 53-line entry + 5 libs (26965ba)install_pacman_wrapper.sh316 → 128-line entry + 4 libs (4eedc17)
Read the 4eedc17 diff first — it is the closest worked example, and three of
its lessons apply directly here:
- Keep path/variable definitions in the ENTRY script, not a
lib/paths.sh. A definitions-only lib assigns without referencing, which is SC2034 on every variable, and suppressions are forbidden. Libs that only reference globals are fine.exportsilences it but leaks the variables into every child process — a behaviour change the trace cannot see. Don't. - The pre-commit hook runs
shfmt -won staged shell files and re-stages them, rewritingcat > "x" << 'EOF'intocat >"x" <<'EOF'. Runshfmt -wyourself first, then verify, so the tested state is the committed state. - Enumerate every test that greps the target's source text before designing
the seams. The pacman split had eight such assertions across two test libs;
one (
chattr +i) was missed on the first pass and failed loudly. Run:grep -rn 'install_leechblock' linux_configuration/tests/ meta/ .github/
Repo-wide jscpd reports 2.51% from the working tree but 1.47% at HEAD in a
clean worktree — the excess is entirely vendored .venv/.ci-mirror-venv
site-packages. Don't chase it; measure in a worktree if you need a real number.
The job: pick a target, then prove it
There is no pre-chosen target this session (see "Ruled out" below for why). The method is what carries over, and it is not negotiable: prove the split by executing the script before and after, and diffing the traces.
# 1. Does anything deploy this file elsewhere? If yes, STOP (see above).
grep -rn '<basename>' linux_configuration/ --include='*.sh' | grep -v '<target>:'
# 2. Baseline at HEAD, BEFORE editing. Fresh, non-existent prefix.
meta/scripts/trace_shell_split.sh <target> --prefix /tmp/pfx-before \
--stub pkill --out /tmp/before.txt
# 3. Run it twice at HEAD and diff, to learn what varies legitimately
# (timestamps, curl progress, the echoed prefix path) before you trust it.
# 4. Split. Then re-trace into a DIFFERENT fresh prefix and diff.
Coverage decides the seams. Whatever the trace does not execute stays in the entry script, because a split that relocates unexecuted code cannot be verified by the diff. Read the trace's stdout against the target's guarded blocks and enumerate what was skipped before choosing where to cut.
Rules that will bite you
- Keep variable ASSIGNMENTS in the entry; move FUNCTION DEFINITIONS freely.
A lib that assigns without referencing is SC2034 on every variable, and
suppressions are forbidden. A lib that only references globals is clean.
exportsilences SC2034 while leaking the variables into every child process — a behaviour change the trace cannot see. Don't. - Verify with
pre-commit run shellcheck --files …, not a bareshellcheck -x. The latter followssource=directives and hid 36 SC2034 findings in a standalone lib; the former is the real gate. - The pre-commit hook runs
shfmt -won staged shell files and re-stages them, rewritingcat > "x" << 'EOF'intocat >"x" <<'EOF'. Runshfmt -wyourself first, then verify, so the tested state is the committed state. - Enumerate every test that greps the target's source text before cutting.
The pacman split had eight such assertions across two test libs; one
(
chattr +i) was missed on the first pass and failed loudly. - Wrapping top-level code in a function changes what
set -esees. A bare[[ -n $x ]] && arr+=("$x")as a function's last statement becomes its return value; append|| trueif so. - New
.shlibs need the executable bit, a#!/bin/bashshebang, and noset -euo pipefail(a sourced lib inherits the caller's strict mode). - Every commit touching code needs an evidence JSON in
docs/superpowers/evidence/; ≥4 staged code files additionally needs a freshdocs/superpowers/contracts/*.json. Validate both withpython3 meta/scripts/validate_contract.py/meta/scripts/validate_evidence.py. - Markdown needs
npx prettier --write— prettier is pre-push only. - Work directly on
main; commit and push.git stashand branch creation are blocked by hooks.git pushrunsci-mirrorand takes minutes — never edit files while a push is running. - Do not wire the file-length pre-commit hook. It must land last, only once
bash ~/utils/scripts/check_file_length.sh --allexits 0. 55 files are still over.
Repo-wide jscpd reports 2.51% from the working tree but 1.47% at HEAD in a
clean worktree — the excess is entirely vendored .venv/.ci-mirror-venv
site-packages. Don't chase it; measure in a worktree if you need a real number.
Read these first
docs/shell-split-verification.md— how a green split is still broken. Now also covers the deployed-copy blocker, the/usr/binbind limit, the unstubbedpkill, processes the prefix cannot contain, and how to compare when the target embeds a timestamp.docs/shell-split-recipes.md— how to actually make a split.- The
4eedc17and26965badiffs — two worked examples of the entry+lib shape.
Definition of done
- The target and every file split out of it are under 250 lines.
- The before/after traces are identical once known-variable patterns are normalized, with every written-file hash unchanged.
- Nothing moved into a lib sits in a region the trace never executed.
- Any test that greps the target's source text still passes, with no assertion weakened.
pre-commit run shellcheckclean,shfmtclean, zero suppressions.- The live system is untouched by the verification runs — check mtimes of whatever the script writes, before and after.
- Committed and pushed, with evidence (and a contract if ≥4 code files).
Ruled out — do not attempt these
install_leechblock.sh(485) — see the top of this file. Traces perfectly, but is deployed as a single file and run from/usr/local/share/digital_wellbeing/on every pacman invocation.setup_thorium_startup.sh(443) — traces to exit 0 with all 8 steps covered and has no deployed copy, so it is mechanically splittable. Do not split it anyway: the thing it configures is dead. Thorium was removed from this machine (chromium/librewolf are used now),/usr/local/bin/thorium-browseris a leftover symlink tobrowser-preexec-wrapper, and the enabledthorium-fitatu-startup.servicefails silently at every boot:thorium-browser: line 33: /https://www.fitatu.com/app/planner: No such file or directory— while systemd still reportsstatus=0/SUCCESS. Splitting a 443-line script to keep a broken autostart under a line cap is the wrong work. Either retarget it at chromium/librewolf or delete it and the unit; ask the user which. That decision is worth more than the split.nvidia_troubleshoot.sh(336). Its trace dies at step 3 becausebackup_filewrites/etc/profile.backup.<stamp>, a new sibling in unbound/etc(a file bind cannot cover siblings, and bare/etcis refused). 239 of its 336 lines never execute. Keeping the unproven ones in the entry — the rule the pacman split established — leaves the entry at ~265, still over the cap.block_compulsive_opening.sh(705) —install_allcopies the running script into/usr/local/bin; same deployed-copy blocker as leechblock.- Blocked on NAMED blockers, not line count:
check_and_enable_services.sh(1337),steam_compatibility.sh(663),libre_translate.sh(488),enforce_vbox_hosts.sh(443). Seerefactor_claude_todo_resume.md.
There is no easy next target. Every remaining file under ~500 lines is now either blocked by a deployed copy, unverifiable by trace, or configures something dead. Pick by re-probing, not by line count, and expect the next win to cost more than the last two.
docs/shell-split-verification.md is itself 272 lines, over the cap. Split it
when someone is doing docs work; it is not urgent and the hook is not wired.
Known pre-existing failure (not yours)
bash linux_configuration/tests/test_security_hardening.sh exits 1 at HEAD
with exactly one failure, ❌ FAIL: Compulsive block wrappers installed. It
belongs to block_compulsive_opening.sh. Capture it before and after your
change and confirm it is unchanged; do not try to fix it.