diff --git a/CLAUDE.md b/CLAUDE.md index 8c34e6f..6d64c41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,3 +80,83 @@ substituted by the corresponding setup script at deploy time. Common placeholder Always grep for `__[A-Z_]*__` in template files before committing to catch unsubstituted placeholders. + + + + +## Headroom Learned Patterns + +*Auto-generated by `headroom learn` on 2026-04-07 — do not edit manually* + +### Podman / Container Stack on TILSIT + +*~3,500 tokens/session saved* + +- `podman-compose` (formula) is required — not included in initial setup; it must be in `config/formulae.txt` alongside `podman` +- The container stack requires `privileged: true` in compose.yml to access `/dev/net/tun` for OpenVPN on macOS +- `CREATE_TUN_DEVICE=true` does NOT work on macOS — remove it; use `privileged: true` instead +- Container compose files live at `~/containers/transmission/` on the operator account on TILSIT + +### SSH to TILSIT + +*~3,000 tokens/session saved* + +- `ssh operator@tilsit.local` requires PATH to be set explicitly: always prefix commands with `export PATH=/opt/homebrew/bin:/opt/homebrew/sbin:$PATH` +- `podman` is not in the default PATH over SSH; use `/opt/homebrew/bin/podman` directly or export PATH first +- `ssh andrewrich@tilsit.local` works for dev/admin tasks; `ssh operator@tilsit.local` is the service account for containers +- rsync to `operator@tilsit.local` paths may require writing to `/tmp` first then `cp` as operator: `rsync file andrewrich@tilsit.local:/tmp/ && ssh operator@tilsit.local 'cp /tmp/file ~/dest/'` + +### Markdown Linting + +*~2,500 tokens/session saved* + +- CI enforces markdownlint; run `npx markdownlint-cli@0.47.0 --disable=MD013 ` before committing markdown files +- Common violations: bold text used as headings (`**Step N:**` must be `### Step N:`), table pipe spacing (MD060), line length MD013 (can be disabled) +- Use `npx markdownlint-cli@0.47.0 docs/ 2>&1 | grep -v MD013 | grep -v MD033` to check only blocking rules + +### Git & Pre-commit Hooks + +*~2,000 tokens/session saved* + +- `plan.md` is in `.gitignore` — never use `git add plan.md`; use `git add -f plan.md` if forced, or just skip it +- Pre-commit hooks run globally from `/Users/andrewrich/.config/pre-commit/config.yaml` and frequently fail for shell scripts due to trailing whitespace/end-of-file issues — run `shellcheck` first, then `git add` without `chmod +x` in the same command +- When pre-commit fails on a commit, retry with `git add && git commit` (without `chmod` in the same line) + +### GitHub CLI (gh) Usage + +*~2,000 tokens/session saved* + +- `gh pr checks --repo ` exits with code 8 when any check is still pending/running — use `grep -E 'fail|pending'` to filter, and don't treat exit-code 8 as a fatal error +- Use `gh api repos///pulls//comments` with a python3 one-liner to extract comment bodies/IDs; the raw JSON is large +- `gh run watch` requires an explicit run-id when not running interactively — use `sleep N && gh pr checks` instead + +### Shellcheck + +*~1,500 tokens/session saved* + +- Always run `shellcheck ` before committing any `.sh` file — pre-commit will block commits with shellcheck warnings +- `config/config.conf.template` is not a shell script; do not shellcheck it (SC2148 false positive) +- Common fixes needed: `!= b` instead of `! a == b` (SC2335), quote array expansions (SC2312), no unquoted variables in path expansions + +### 1Password CLI (op) Paths + +*~1,500 tokens/session saved* + +- PIA credentials are stored at `op://Personal/Private Internet Access/user` and `op://Personal/Private Internet Access/pass` (literal field names, not IDs) +- Use `op read 'op://Personal/Private Internet Access/user'` directly — do not look up field IDs or use `op item get` JSON parsing for these fields + +### File Edit Guards + +*~1,000 tokens/session saved* + +- Always `Read` a file immediately before `Edit` if any other tool (linter, ssh, git) may have touched it since last read — the agent will get `File has been modified since read` or `File has not been read yet` errors otherwise +- Do not batch `chmod +x` and `git add` in the same shell command when pre-commit hooks are active — stage and commit separately + +### Claude Plugins / Skills on Remote Machine + +*~1,000 tokens/session saved* + +- When setting up Claude CLI on a new machine (TILSIT), plugins/skills must be rsynced from the dev machine before first launch: `rsync -av --exclude='.git' --exclude='*.sock' ~/.claude/plugins/ :~/.claude/plugins/` +- `rsync` to `/dev/null` or socket files will fail — always add `--exclude='*.sock'` when syncing `.claude/plugins/` + + diff --git a/app-setup/templates/process-media.command b/app-setup/templates/process-media.command index 7b6f1a8..1b98bdf 100755 --- a/app-setup/templates/process-media.command +++ b/app-setup/templates/process-media.command @@ -2,6 +2,7 @@ # Strict mode set -euo pipefail +# nosemgrep: bash.lang.security.ifs-tampering.ifs-tampering -- safe strict-mode idiom IFS=$'\n\t' # Change to script directory diff --git a/app-setup/templates/transmission-done.sh b/app-setup/templates/transmission-done.sh index cbeaa5a..a124def 100755 --- a/app-setup/templates/transmission-done.sh +++ b/app-setup/templates/transmission-done.sh @@ -23,6 +23,7 @@ export PATH # Strict mode (without -e to allow error logging instead of immediate exit) set -uo pipefail +# nosemgrep: bash.lang.security.ifs-tampering.ifs-tampering -- safe strict-mode idiom IFS=$'\n\t' # Signal handling diff --git a/app-setup/transmission-filebot-setup.sh b/app-setup/transmission-filebot-setup.sh index 103b0a4..fbfcd9d 100755 --- a/app-setup/transmission-filebot-setup.sh +++ b/app-setup/transmission-filebot-setup.sh @@ -2,6 +2,7 @@ # Strict mode set -euo pipefail +# nosemgrep: bash.lang.security.ifs-tampering.ifs-tampering -- safe strict-mode idiom IFS=$'\n\t' # Debug mode: Set DEBUG_MODE=1 to enable verbose logging including credentials diff --git a/prep-airdrop.sh b/prep-airdrop.sh index 607cb40..efd5bbd 100755 --- a/prep-airdrop.sh +++ b/prep-airdrop.sh @@ -262,6 +262,7 @@ copy_dir_with_manifest() { local dest_dir_full="${OUTPUT_PATH}/${dest_dir_relative}" # Prepare array of exception dirs if except_dirs_string is non-empty + # nosemgrep: bash.lang.security.ifs-tampering.ifs-tampering -- locally scoped, intentional local IFS='|' read -r -a except_dirs <<<"${except_dirs_string}"