Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:learn:start -->

<!-- markdownlint-disable MD036 -->
## 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 <file>` 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/<file> 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 <file> && git commit` (without `chmod` in the same line)

### GitHub CLI (gh) Usage

*~2,000 tokens/session saved*

- `gh pr checks <pr> --repo <owner/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/<owner>/<repo>/pulls/<pr>/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 <file>` 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/ <host>:~/.claude/plugins/`
- `rsync` to `/dev/null` or socket files will fail — always add `--exclude='*.sock'` when syncing `.claude/plugins/`

<!-- headroom:learn:end -->
1 change: 1 addition & 0 deletions app-setup/templates/process-media.command
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions app-setup/templates/transmission-done.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions app-setup/transmission-filebot-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions prep-airdrop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"

Expand Down
Loading