# CLAUDE.md Working notes for Claude Code on this project. Read before editing. ## What this is `sbo-batch-test`: a single self-contained bash script that batch-tests SlackBuilds against a clean Slackware 15.0 overlay chroot. The user is an SBo (SlackBuilds.org) maintainer on Slackware64-current who needs to verify builds against 15.0 stable without booting a VM. Full spec lives in `claude-code-prompt-sbo-batch-tester.txt`. Reference mount machinery in `overlay-chroot.sh` (Jeremy Hansen / bassmadrigal). User-facing docs in `README.md`. ## Conventions (the user cares about these) - **No em dash character** in any prose, comments, README, or commit messages. Use commas or periods. This is a hard rule. - This session runs with ponytail (laziest working solution) and caveman (terse replies) modes. Code stays normal; chat is terse. - Build incrementally. The user wants to review the dep resolver and the mount/teardown core before trusting the rest. Do not balloon scope. - Do NOT default to agreement. If a choice is fragile or wrong, say so and propose better with reasoning. ## Hard constraints (do not violate) - **Resolution is LOCAL-tree-only.** Never add network/sbopkg resolution. The local SBo tree is the single source of truth (contains unpublished personal/pentesting packages). Settled, not open for revisiting. - **overlay lowerdir must be LOCAL.** `SLACKWARE_BASE` is a local fs path. `LOCAL_MIRROR_15` is the NFS mountpoint, a package SOURCE only (content read via the derived `MIRROR_TREE`). overlayfs over NFS lowerdir is fragile. The script guards against pointing base under the mirror. The mirror fstab entry is `noauto`; the script checks `mountpoint -q` and auto-mounts (`mount $LOCAL_MIRROR_15`) if absent, left mounted after the run. - **Do not wrap/drive slackrepo.** Separate pipeline, keep independent. - **Shares host kernel.** No kernel-module testing claims. - Runs as root only (overlay + chroot). - Per-target disposable overlay: each category target tests against pristine 15.0. Built packages are throwaway. ## Architecture (single file: sbo-batch-test) Top-to-bottom layout: 1. **CONFIG block** - empty defaults plus a `source` of the external config file. Real values live OUTSIDE the script in `$SBO_BATCH_CONFIG` (default `~/.config/sbo-batch-tester/config`, i.e. `/root/.config/...` since it runs as root); `config.example` in the repo is the template. The block sets empty defaults (`SLACKWARE_BASE LOCAL_MIRROR_15 SBO_TREE_ROOTS`) plus `CHROOT_LOCATION=/tmp LOG_ROOT PKG_CACHE='' VERSION=15.0`, sources the config if present (NOT an error if missing, so the script stays sourceable by `test-logic.sh`), then derives `MIRROR_TREE`. `require_config` (called by `validate_env` and `init_base`) is what hard-fails a real run when the config is absent or has not set the required paths. `LOCAL_MIRROR_15` is the NFS mountpoint; the tree is one level down, `MIRROR_TREE="$LOCAL_MIRROR_15/slackware64-$VERSION"` (holds ChangeLog.txt, slackware64/, patches/). All mirror content reads use `MIRROR_TREE`; the mountpoint check, auto-mount, and the under-mirror guard use `LOCAL_MIRROR_15`. 2. **Globals / flags** - `USE_COLOR DRY_RUN WITH_X JOBS`, status maps (`ST_STATUS ST_REASON ST_TIME ST_README`), `ACTIVE_MOUNTS[]`. 3. **usage / init_color / parse_args / validate_env** - fail-fast startup checks with copy-pasteable hints. 4. **init_base** - `--init-base` mode only. First-time populate: `installpkg --root` the full `slackware64/*/*.t?z` set into `SLACKWARE_BASE`, seed the `last-base-update` marker, exit. Runs its own checks (root, mirror mounted /auto-mount, ChangeLog.txt present, not under mirror) since the base does not exist yet; refuses to clobber a populated base. Wired in `main` before `validate_env`, exits 0. installpkg/upgradepkg run with `--terse`. **update_base** - patches local base from mirror's `patches/packages/` when ChangeLog head differs (reused from reference script). 5. **SBo tree lookup** - `find_slackbuild_dir`, `category_of`, `pkg_key`, `read_requires`. 6. **Dependency resolution** - `resolve_target` -> `_resolve_visit` (DFS topo sort + cycle detection). Outputs `RESOLVED_ORDER[]`, records `UNMET[]`, `CYCLES[]`, `HAS_README[]`. `installed_in_base` checks base package db. 7. **Overlay lifecycle** - `setup_overlay` (echoes tmpdir, mounts overlay + binds), `teardown_overlay` (idempotent, correct reverse order), `cleanup_trap` (EXIT/INT/TERM, unwinds all live overlays). 8. **build_one** - copies SlackBuild into overlay, runs download/md5/build/ installpkg INSIDE the chroot non-interactively via heredoc, reads back a status token file, sets status maps. Logs resolved .info env up front and the installed file list (from /var/log/packages) after installpkg, so the overlay is fully disposable. 9. **run_target** - fresh overlay per target, builds resolved chain in order, marks `BLOCKED-BY-DEP` on dependents of failures, tears down. 10. **print_summary** - color screen recap + plain `summary.log`. 11. **main** - parse, validate, make `RUN_DIR`, update base, collect targets (single-package or category-folder), run each. ## Teardown order (do not reorder) pts -> dev/proc/sys -> resolv.conf -> dbus machine-id -> overlay last. Matches the reference script. Idempotent (mountpoint-guarded), trap-registered so a mid-target abort still unwinds. `ACTIVE_MOUNTS[]` tracks live overlays. ## Status values `SUCCESS CACHED DOWNLOAD-FAILED MD5-MISMATCH BUILD-FAILED INSTALL-FAILED BLOCKED-BY-DEP UNMET-DEP`. `CACHED` = a dependency installed from the persistent package cache instead of being rebuilt (target is never CACHED). `%README%` recorded separately as a reminder flag, not a status. ## What is verified vs not - **Verified by self-check** (`test-logic.sh` in repo, `bash test-logic.sh`, no VM needed): topo order, `%README%` recording, unmet-dep, cycle detection, and BLOCKED-BY-DEP propagation (`depends_on_failed`, including the transitive one-hop cascade), and the package-cache logic (`cache_decision`/`cache_path`/`cache_store`/`version_of`). The check builds a fake SBo tree and sources the script with config overridden AFTER sourcing (sourcing re-runs the CONFIG block, which resets the vars to empty defaults and may source the external config, so test vars must be set after the `source`). The test runs as a normal user, so `~/.config/sbo-batch-tester/config` usually does not exist and is not sourced; even if it did, the post-source overrides win. Gotchas baked in: do not name the dead list `failed` (collides with `depends_on_failed`'s `local -n failed`), and `ok`/`bad` must `return 0` (`((x++))` returns nonzero when x was 0). - **Verified on the build system**: `--init-base` populate ran clean against the real mirror (mountpoint check, auto-mount of the noauto NFS entry, `MIRROR_TREE` derivation, external config sourcing, full installpkg --terse set). The external-config split (`require_config`, missing/incomplete errors) was checked in isolation too. The full build flow ran end to end on a real target (`feh` + its dep `imlib2`): overlay mount, chroot build, forced `OUTPUT=/sbo-work/output`, per-package installpkg, teardown, summary, and persistent logs. The package cache was verified live: a first run populated `$PKG_CACHE///` with one .txz per prog, a re-run installed the cached dep (status `CACHED`, ~1s vs ~27s build) while the target rebuilt fresh, and the cached/build-new/target labels and the cached count rendered correctly. `update_base` reported "Base is up-to-date" (no patch pending), so patching and the on-patch cache wipe ran their up-to-date branch only. - **NOT runnable-tested** (no occasion yet): an actual base patch via `update_base` (mirror was up-to-date) and therefore the on-patch cache wipe firing, and the `bump: OLD -> NEW` eviction path on a real version change (covered by the self-check, not yet seen on hardware). Logic mirrors the reference script. ## Known shortcuts (ponytail: comments in source) - `depends_on_failed`: direct-REQUIRES check only. Transitive blocking still works because the loop runs in topo order, so a failure propagates one hop per package. ## Open items / TODO - **`--keep` removed (resolved).** No keep-overlay option. Overlay is always torn down. Rationale: per-package logs now capture full build/install output, resolved `.info` env, and the installed file list, so the overlay holds nothing worth retaining. Do not re-add `--keep` without a reason logs cannot cover. - **Package cache (DONE).** Persistent `$PKG_CACHE` (config var, empty = disabled), SBo-tree layout `//--...txz`, one .txz per prog. Key = prog+version (build/arch/tag ignored). The named target always builds fresh and refreshes the cache; deps install from cache on a version match (status `CACHED`), else build and cache. Cache wiped when `update_base` patches. Pure `cache_decision`/`cache_path`/`cache_store`/`version_of` covered by `test-logic.sh`. Spec: docs/superpowers/specs/2026-06-24-package-cache-design.md. - **"all" mode**: build every package across all SBo roots. Extension point in `main` where `targets` is populated. TODO marker in source. - **queue/list-file mode**: build a named list. Same extension point. - **`-j` parallelism**: flag parsed, currently no-op. TODO marker in source. ## How to run the self-check ```sh bash test-logic.sh ``` Covers resolution + BLOCKED-BY-DEP. Extend it (not /tmp scratch files) when adding logic. Anything VM-dependent (overlay, chroot, installpkg) is out of its reach by design.