From 226ac906a2a78b2cd58914c931349a6336e56f1f Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Thu, 22 Oct 2026 09:21:28 -0400 Subject: [PATCH] Updates --- common/bin/pbuild | 2 +- common/bin/pindex | 2 +- common/bin/play | 2 +- common/bin/psearch | 2 +- common/claude/CLAUDE.md | 65 +++++++++- .../-home-connor-projects-news/MEMORY.md | 2 +- .../news-rewrite-status.md | 6 +- .../news-triage-v02-gotchas.md | 4 +- .../connor-homelab-deploy.md | 2 +- .../runway-commit-style.md | 2 +- .../runway-send-screenshots.md | 2 +- .../runway-v2-build-plan.md | 4 +- common/claude/memory/-home-connor/MEMORY.md | 18 ++- .../-home-connor/crowdsec-bans-own-devices.md | 30 +++++ .../discuss-before-adding-content.md | 15 +++ .../dotbot-force-deletes-live-dirs.md | 33 +++++ .../litellm-extra-body-reasoning.md | 64 ++++++++++ .../-home-connor/llamacpp-offload-method.md | 48 ++++++++ .../-home-connor/local-code-autocomplete.md | 71 +++++++++++ .../memory/-home-connor/mainframe-bad-core.md | 39 ++++++ .../-home-connor/nas-dual-homed-wifi-path.md | 48 ++++++++ .../-home-connor/nfs-mountpoints-immutable.md | 49 ++++++++ .../nfs-pnfs-scsi-layout-wedge.md | 47 +++++++ .../nfs-root-squash-loses-mtimes.md | 35 ++++++ .../-home-connor/npu-setting-clamps-memory.md | 30 +++++ .../-home-connor/owui-image-generation.md | 31 +++++ .../pattern-kill-matches-wrong-process.md | 35 ++++++ .../podman-compose-bind-mount-restart.md | 8 ++ .../podman-nfs-bind-rprivate-blind.md | 68 +++++++++++ .../reasoning-tokens-vs-completion-tokens.md | 38 ++++++ common/claude/settings.json | 7 +- .../claude/skills/rust-crate-release/SKILL.md | 46 +++++++ common/nvim/init.lua | 4 + common/qutebrowser/quickmarks | 2 +- hosts/mainframe/bin/stream | 9 +- hosts/mainframe/sunshine/apps.json | 32 +++++ hosts/mainframe/sway/headless.conf | 115 +++++++++++++++--- meta/hosts/mainframe.conf.yaml | 2 + meta/hosts/sctfw004.conf.yaml | 1 + packages/mainframe.aur | 1 + packages/mainframe.pacman | 9 ++ 41 files changed, 992 insertions(+), 38 deletions(-) create mode 100644 common/claude/memory/-home-connor/crowdsec-bans-own-devices.md create mode 100644 common/claude/memory/-home-connor/discuss-before-adding-content.md create mode 100644 common/claude/memory/-home-connor/dotbot-force-deletes-live-dirs.md create mode 100644 common/claude/memory/-home-connor/litellm-extra-body-reasoning.md create mode 100644 common/claude/memory/-home-connor/llamacpp-offload-method.md create mode 100644 common/claude/memory/-home-connor/local-code-autocomplete.md create mode 100644 common/claude/memory/-home-connor/mainframe-bad-core.md create mode 100644 common/claude/memory/-home-connor/nas-dual-homed-wifi-path.md create mode 100644 common/claude/memory/-home-connor/nfs-mountpoints-immutable.md create mode 100644 common/claude/memory/-home-connor/nfs-pnfs-scsi-layout-wedge.md create mode 100644 common/claude/memory/-home-connor/nfs-root-squash-loses-mtimes.md create mode 100644 common/claude/memory/-home-connor/npu-setting-clamps-memory.md create mode 100644 common/claude/memory/-home-connor/owui-image-generation.md create mode 100644 common/claude/memory/-home-connor/pattern-kill-matches-wrong-process.md create mode 100644 common/claude/memory/-home-connor/podman-nfs-bind-rprivate-blind.md create mode 100644 common/claude/memory/-home-connor/reasoning-tokens-vs-completion-tokens.md create mode 100644 common/claude/skills/rust-crate-release/SKILL.md diff --git a/common/bin/pbuild b/common/bin/pbuild index b77d909..de778ef 100755 --- a/common/bin/pbuild +++ b/common/bin/pbuild @@ -1,2 +1,2 @@ #!/bin/sh -exec /home/connor/docs/projects/playlists/target/release/playlists build "$@" +exec /home/connor/projects/playlists/target/release/playlists build "$@" diff --git a/common/bin/pindex b/common/bin/pindex index 52f2600..8c7cf02 100755 --- a/common/bin/pindex +++ b/common/bin/pindex @@ -1,2 +1,2 @@ #!/bin/sh -exec /home/connor/docs/projects/playlists/target/release/playlists index "$@" +exec /home/connor/projects/playlists/target/release/playlists index "$@" diff --git a/common/bin/play b/common/bin/play index ea191b9..9297526 100755 --- a/common/bin/play +++ b/common/bin/play @@ -1,2 +1,2 @@ #!/bin/sh -/home/connor/docs/projects/playlists/target/release/playlists build -m -s "$@" && ncmpcpp +/home/connor/projects/playlists/target/release/playlists build -m -s "$@" && ncmpcpp diff --git a/common/bin/psearch b/common/bin/psearch index 9927c3c..372cf32 100755 --- a/common/bin/psearch +++ b/common/bin/psearch @@ -1,2 +1,2 @@ #!/bin/sh -exec python3 /home/connor/docs/projects/playlists/scripts/search.py "$@" +exec python3 /home/connor/projects/playlists/scripts/search.py "$@" diff --git a/common/claude/CLAUDE.md b/common/claude/CLAUDE.md index 6d02487..274b3ae 100644 --- a/common/claude/CLAUDE.md +++ b/common/claude/CLAUDE.md @@ -92,9 +92,7 @@ driven by cache-read volume and context size far more than by model choice. ## The dotfiles repo and the fleet -`~/.dotfiles` is **one branch linked onto four machines** by dotbot. Shared -config is in `common/`, per-machine config in `hosts//`, and what gets -linked where is `meta/base.conf.yaml` plus `meta/hosts/.conf.yaml`. +`~/.dotfiles` is **one branch linked onto four machines** by dotbot. There are deliberately no per-host branches: the layout this replaced was three machines with two histories that had never met, and host layers cost a few small files and remove the merge entirely. @@ -152,6 +150,64 @@ Five things about this repo that will cost you an hour: `~/.dotfiles/common/claude/CLAUDE.md`. Editing it edits the repo, so a change here wants a commit and reaches every machine at the next `install`. +## Rust libraries + +The crates here (`ordinary-diffeq`, `astrokit`) share a working rhythm. Follow it +in a new one. + +1. **One coherent change at a time, and the commit is Connor's to make.** Finish + the piece of work, leave it in the working tree, show what changed, and stop. + Don't commit, push, tag or `cargo publish` unless the request says to. Tests + passing is not the same as reviewed. + +2. **Zero warnings, zero fmt diff, every time.** The bar is the whole tree, not + the lines you touched: + - `cargo fmt --check` with no output. + - `cargo clippy --all-targets -- -D warnings`. + - `cargo doc --no-deps` with no warnings at all. + - `cargo test`, which runs the doctests too. Include the readme as the crate's + front page with `#![doc = include_str!("../readme.md")]` so its examples are + compiled and cannot drift from the API. + - `cargo test --no-default-features` if the crate has feature flags. + + A warning that was already there still counts. Fix it in the same pass and say + so. Three rustdoc warnings sat in `ordinary-diffeq` across four releases + because each session read them as someone else's problem. + +3. **Run `cargo fmt` and keep the tree clean.** If a repo isn't clean yet, clean + it in its own commit rather than letting formatting ride along with a change — + but clean it. Where rustfmt would wreck something laid out on purpose, a + Butcher tableau or a table of interpolation coefficients, put + `#[rustfmt::skip]` on that item with a one-line reason; `--check` honors it, so + the tree stays clean and the table stays readable. Checking one file is + `rustfmt --check --edition 2021 `, because `cargo fmt --check -- ` + ignores the file argument and does the whole workspace. + +4. **Hooks, split by what they cost.** Measure before deciding where a check goes; + on `ordinary-diffeq`'s warm tree `cargo fmt --check` is 0.1s, clippy 0.6s and + `cargo test --lib` 0.6s, but the full `cargo test` is 16s because every doctest + links its own binary. So `pre-commit` gets the first three and `pre-push` gets + the doctests and `cargo doc`. Keep the benches and anything networked out of + both — a hook that takes a minute is one that gets `--no-verify`. Git does not + track where it looks for hooks, so commit them to `hooks/` and point each clone + at it once with `git config core.hooksPath hooks`. + +5. **Benchmarking, when a change could plausibly cost something.** criterion, + and measure rather than assert — the method and mainframe's noise floor are + in the `rust-crate-release` skill. +6. **In 0.x, a breaking change is a minor bump.** Changing a trait method + signature took `ordinary-diffeq` from 0.5.0 to 0.6.0. + +7. **The release process** — branch, bump `Cargo.toml` alone, merge `--no-ff`, + annotated tag, publish. The exact sequence is in the `rust-crate-release` + skill. A publish cannot be undone; a bad version can only be yanked. +8. **Each feature keeps a plan that outlives it, as an issue on the Gitea + remote.** Don't create `roadmap/features/` files in a repo that doesn't + already have them. Comment on the issue when the work lands: what was done, + what was measured, what is still a known limitation, and which approach was + tried first and why it was wrong. The last of those is what saves the next + session. Leave the issue open until given permission to close it. + ## General - `~/config/` and `~/docs/` are deliberately untracked. Back up edits there by @@ -175,3 +231,6 @@ here wants a commit and reaches every machine at the next `install`. good idea. No bold for emphasis, no aphorisms, no "X is not Y" constructions, no sentence whose only job is to make a point land. Concrete examples that explain something are welcome; salesmanship is not. +- **No AI attribution in commit messages.** No `Co-Authored-By: Claude`, no + session-link trailer, no model named as an author — subject and body only. + This overrides the harness default, which asks for the trailer. diff --git a/common/claude/memory/-home-connor-projects-news/MEMORY.md b/common/claude/memory/-home-connor-projects-news/MEMORY.md index 21d3eed..cc5c92a 100644 --- a/common/claude/memory/-home-connor-projects-news/MEMORY.md +++ b/common/claude/memory/-home-connor-projects-news/MEMORY.md @@ -1,2 +1,2 @@ -- [News rewrite status](news-rewrite-status.md) — old Rust system still in production, Python rewrite at ~/docs/projects/news; read profile/CALIBRATION.md before touching scoring constants +- [News rewrite status](news-rewrite-status.md) — old Rust system still in production, Python rewrite at ~/projects/news; read profile/CALIBRATION.md before touching scoring constants - [news_triage v0.2 gotchas](news-triage-v02-gotchas.md) — SIGHUP won't reload sources, healthcheck lies, ntfy buttons dead by design diff --git a/common/claude/memory/-home-connor-projects-news/news-rewrite-status.md b/common/claude/memory/-home-connor-projects-news/news-rewrite-status.md index fd69cd2..f68cdb6 100644 --- a/common/claude/memory/-home-connor-projects-news/news-rewrite-status.md +++ b/common/claude/memory/-home-connor-projects-news/news-rewrite-status.md @@ -1,13 +1,13 @@ --- name: news-rewrite-status -description: State of the news aggregator rewrite - old system still running, new one at ~/docs/projects/news, design rationale lives in profile/CALIBRATION.md +description: State of the news aggregator rewrite - old system still running, new one at ~/projects/news, design rationale lives in profile/CALIBRATION.md metadata: type: project --- -Two systems exist. **`~/docs/projects/news_triage`** (Rust, v0.2, container +Two systems exist. **`news_triage`** (Rust, v0.2, container `connor_news-backend_1`, config `~/config/news/config.toml`) is **still running in -production** and serving Connor's notifications. **`~/docs/projects/news`** is the +production** and serving Connor's notifications. **`~/projects/news`** is the greenfield Python rewrite, started 2026-09-08. Keep the old one running until cutover; Connor chose **no data import** into the new schema. diff --git a/common/claude/memory/-home-connor-projects-news/news-triage-v02-gotchas.md b/common/claude/memory/-home-connor-projects-news/news-triage-v02-gotchas.md index 12acfd3..06cf0b2 100644 --- a/common/claude/memory/-home-connor-projects-news/news-triage-v02-gotchas.md +++ b/common/claude/memory/-home-connor-projects-news/news-triage-v02-gotchas.md @@ -5,7 +5,9 @@ metadata: type: project --- -Traps in the still-running `connor_news-backend_1` (from `~/docs/projects/news_triage`), +Traps in the still-running `connor_news-backend_1` (built from the `news_triage` source tree, +which is no longer on disk — it was not carried into `~/projects` on 2026-10-02; +the running container is all that is left), each of which costs a debugging cycle to rediscover: - **SIGHUP does not reload sources.** `watch_sighup` swaps the config, and per-cycle reads diff --git a/common/claude/memory/-home-connor-projects-runway/connor-homelab-deploy.md b/common/claude/memory/-home-connor-projects-runway/connor-homelab-deploy.md index d0f9191..6ec3823 100644 --- a/common/claude/memory/-home-connor-projects-runway/connor-homelab-deploy.md +++ b/common/claude/memory/-home-connor-projects-runway/connor-homelab-deploy.md @@ -42,5 +42,5 @@ Caddy bind-mounts host directories, so a deploy that swaps a directory by renaming it is invisible until Caddy restarts — the mount holds the inode. Mount the parent of the directory being replaced. -Runway's own arrangement is in `~/docs/projects/runway/deploy/README.md`. +Runway's own arrangement is in `~/projects/runway/deploy/README.md`. See [[runway-v2-build-plan]] and [[verify-before-claiming]]. diff --git a/common/claude/memory/-home-connor-projects-runway/runway-commit-style.md b/common/claude/memory/-home-connor-projects-runway/runway-commit-style.md index 5f21f47..2751f84 100644 --- a/common/claude/memory/-home-connor-projects-runway/runway-commit-style.md +++ b/common/claude/memory/-home-connor-projects-runway/runway-commit-style.md @@ -8,7 +8,7 @@ metadata: modified: 2026-08-26T17:49:34.494Z --- -Commits in `~/docs/projects/runway` are small (one per milestone), plainly worded, and authored +Commits in `~/projects/runway` are small (one per milestone), plainly worded, and authored as Connor Johnstone. **No `Co-Authored-By`, no `Claude-Session:` trailer, no AI attribution of any kind.** diff --git a/common/claude/memory/-home-connor-projects-runway/runway-send-screenshots.md b/common/claude/memory/-home-connor-projects-runway/runway-send-screenshots.md index 1d5bcaf..dcad997 100644 --- a/common/claude/memory/-home-connor-projects-runway/runway-send-screenshots.md +++ b/common/claude/memory/-home-connor-projects-runway/runway-send-screenshots.md @@ -8,7 +8,7 @@ metadata: modified: 2026-08-27T00:52:30.624Z --- -Do **not** routinely send UI screenshots for `~/docs/projects/runway`. Connor +Do **not** routinely send UI screenshots for `~/projects/runway`. Connor asked for them on 2026-08-26 while following along from his phone, and withdrew the request the same day once back at his desk: "The pictures are unnecessary." diff --git a/common/claude/memory/-home-connor-projects-runway/runway-v2-build-plan.md b/common/claude/memory/-home-connor-projects-runway/runway-v2-build-plan.md index e7ad52f..c698d03 100644 --- a/common/claude/memory/-home-connor-projects-runway/runway-v2-build-plan.md +++ b/common/claude/memory/-home-connor-projects-runway/runway-v2-build-plan.md @@ -8,7 +8,9 @@ metadata: modified: 2026-08-26T17:49:32.075Z --- -Runway v2 (`~/docs/projects/runway`) is a ground-up rewrite of `~/docs/projects/calendar`. +Runway v2 (`~/projects/runway`) is a ground-up rewrite of the old `calendar` project, whose +working tree was deleted on 2026-10-02 (it lives on only in its git remote +`connor@git.rcjohnstone.com:connor/calendar.git`). The build plan lives OUTSIDE the repo at `~/.claude/plans/ok-i-ve-filled-it-optimized-meerkat.md` — milestones M1–M28 across 6 phases, one commit per milestone. `docs/legacy-audit.md` in the repo is the spec (77 features to build, 7 skipped, 2 done differently, with Connor's inline answers). diff --git a/common/claude/memory/-home-connor/MEMORY.md b/common/claude/memory/-home-connor/MEMORY.md index 8d5ec41..398c3e0 100644 --- a/common/claude/memory/-home-connor/MEMORY.md +++ b/common/claude/memory/-home-connor/MEMORY.md @@ -1,6 +1,22 @@ - [No AI commit attribution](no-ai-commit-attribution.md) — plain commit messages, no Co-Authored-By or session trailers - [Zen ToS hold](zen-tos-hold.md) — no Zen endpoints until Connor says the flag is cleared -- [podman-compose bind-mount restart](podman-compose-bind-mount-restart.md) — config-file edits need an explicit podman restart; compose won't do it +- [podman-compose bind-mount restart](podman-compose-bind-mount-restart.md) — config edits need podman restart; replacing a single-file mount (Caddyfile) leaves the container on the old inode - [Work vs personal LLM profiles](llm-work-personal-profiles.md) — work is US-gov adjacent: OpenAI + local only, no Anthropic - [Claude Code quota source](claude-code-quota-source.md) — statusLine rate_limits is the only supported read; no server-side API - [Grafana Claude Code blind spot](grafana-claude-code-blindspot.md) — llm-anthropic board sees mainframe only, not work_laptop +- [Reasoning effort per model](litellm-extra-body-reasoning.md) — GLM-5.3 defaults to reasoning_effort=max and cannot disable thinking; gpt-oss needs chat_template_kwargs via extra_body +- [Reasoning vs completion tokens](reasoning-tokens-vs-completion-tokens.md) — reasoning comes out of max_tokens, and providers disagree about counting it +- [Open WebUI image generation](owui-image-generation.md) — works via LiteLLM; the default 512x512 size is rejected by every image model +- [Local code autocomplete](local-code-autocomplete.md) — FIM via /v1/completions; if it dies check rbw lock first, and never tune the coder model for decode +- [NPU setting clamps memory](npu-setting-clamps-memory.md) — MSI NPU/AI BIOS options cost 17% DRAM bandwidth and the NPU is unusable by llama.cpp anyway. +- [mainframe bad core](mainframe-bad-core.md) — APIC 20 (cpu10, an E-core) throws fatal MCEs; offlined by a unit, RMA candidate. +- [crowdsec bans own devices](crowdsec-bans-own-devices.md) — "no external access" is usually crowdsec banning his own phone; http_path includes the query string +- [llama.cpp offload method](llamacpp-offload-method.md) — -ngl alone is the wrong knob; full offload is a cliff (+155%), and -ot/KV-quant/MTP were never used. +- [Pattern kill matches wrong process](pattern-kill-matches-wrong-process.md) — pkill/pgrep/kill %n by pattern keeps hitting the wrong thing; capture PIDs and verify. +- [NFS pNFS SCSI layout wedge](nfs-pnfs-scsi-layout-wedge.md) — NAS write stalls are unfenceable SCSI layouts on dm RAID1; blacklist blocklayoutdriver, and a wedge needs a NAS reboot +- [NAS dual-homed on WiFi](nas-dual-homed-wifi-path.md) — NAS traffic silently took WiFi not the 2.5GbE wire; ARP flux + missing wired route. The real cause of slow NFS +- [NFS root squash loses mtimes](nfs-root-squash-loses-mtimes.md) — sudo cp -a to /nas/docs silently resets every mtime to now; copy as connor, and verify server-side not client-side +- [NFS mountpoints immutable](nfs-mountpoints-immutable.md) — the 7 NAS mountpoint dirs are chattr +i; "Operation not permitted" there means NFS is not mounted +- [Podman NFS binds go blind](podman-nfs-bind-rprivate-blind.md) — a mount landing after container start leaves containers permanently empty; podman ps looks fine +- [dotbot force deletes live dirs](dotbot-force-deletes-live-dirs.md) — install replaces a real dir at a link target; the Claude memory dir is now one of them +- [Discuss before adding content](discuss-before-adding-content.md) — in his notes, record what he said; suggest extras in chat, add only on agreement diff --git a/common/claude/memory/-home-connor/crowdsec-bans-own-devices.md b/common/claude/memory/-home-connor/crowdsec-bans-own-devices.md new file mode 100644 index 0000000..042ced2 --- /dev/null +++ b/common/claude/memory/-home-connor/crowdsec-bans-own-devices.md @@ -0,0 +1,30 @@ +--- +name: crowdsec-bans-own-devices +description: crowdsec http-probing bans Connor's own phone because http_path keeps the query string, so one app session looks like dozens of distinct 404s +metadata: + type: project +--- + +On mainframe, "can't reach my services from outside the LAN" has been crowdsec +banning Connor's *own* device, not DNS/DDNS/ports/TLS. Confirmed 2026-09-27. + +`crowdsecurity/caddy-logs` sets `evt.Meta.http_path` to `request.uri` — the full +URI **including the query string**. `crowdsecurity/http-probing` keys +`distinct: evt.Meta.http_path` with `capacity: 10`, so any app that hits ONE +endpoint with many different query params and gets 4xx back looks like dozens of +distinct probed paths and overflows the bucket. The nft firewall-bouncer then +DROPS that IP for 4h: every service times out from that address while the LAN is +completely unaffected — which is what makes it read as a port-forward or DNS fault. + +Worked example: Substreamer/air-sonic requesting `/rest/getCoverArt.view?id=al-N&size=N` +against shanty, which 404s art it doesn't have → 22 distinct URIs → ban. +Fixed by `~/config/crowdsec/parsers/s02-enrich/zz-shanty-subsonic-whitelist.yaml` +(scoped to 404 + `/rest/` on shanty only). Editing it needs +`sudo podman restart connor_crowdsec_1` — see [[podman-compose-bind-mount-restart]]. + +Diagnostic order that actually works: `cscli decisions list` and +`grep -c ` against `sudo nft list table ip crowdsec` FIRST. Note +`cscli decisions list -i ` did not filter as expected — it printed CAPI +entries too, so the nft set membership check is the reliable one. Beware: +`http-crawl-non_statics` is NOT the same hazard — it keys `distinct` on +`file_name`, so normal streaming doesn't trip it. diff --git a/common/claude/memory/-home-connor/discuss-before-adding-content.md b/common/claude/memory/-home-connor/discuss-before-adding-content.md new file mode 100644 index 0000000..3ca0f52 --- /dev/null +++ b/common/claude/memory/-home-connor/discuss-before-adding-content.md @@ -0,0 +1,15 @@ +--- +name: discuss-before-adding-content +description: "In Connor's notes (e.g. ~/docs/food), suggest additions in chat; only write what he asked for or agreed to" +metadata: + node_type: memory + type: feedback + originSessionId: 9b833073-62cf-4dbb-b24f-eeba7dbe6c33 + modified: 2026-10-21T21:03:05.671Z +--- + +When recording Connor's notes, write down what he said; raise my own ideas (extra shopping items, extra recipes) in chat and add them only after he agrees. + +**Why:** In the ~/docs/food session (2026-10-21) I added items to his shopping list unprompted and he objected: "why would you just add additional foods rather than talk to me about it?" + +**How to apply:** Record his items as given. When I have a suggestion, describe it briefly in the reply and ask before writing it into the files. Keep notes terse; he wants to actually read them. diff --git a/common/claude/memory/-home-connor/dotbot-force-deletes-live-dirs.md b/common/claude/memory/-home-connor/dotbot-force-deletes-live-dirs.md new file mode 100644 index 0000000..ebdc518 --- /dev/null +++ b/common/claude/memory/-home-connor/dotbot-force-deletes-live-dirs.md @@ -0,0 +1,33 @@ +--- +name: dotbot-force-deletes-live-dirs +description: dotbot's link defaults are force+relink, so install DELETES a real file or directory at any link target — including the Claude memory dir, which is now a linked directory +metadata: + type: project +--- + +Every dotbot config in `~/.dotfiles` restates `link: {create: true, relink: +true, force: true}` (`meta/base.conf.yaml` and each `meta/hosts/*.conf.yaml` — +dotbot does not carry a defaults block between files). `force: true` means +`install` removes whatever real file or directory sits at a link target and +replaces it with the symlink, without asking. + +As of 2026-10-03 that now includes a *directory* of live data: + + ~/.claude/projects/-home-connor/memory: common/claude/memory/-home-connor + +**Why this matters:** before that link existed, the repo held 6 plain copies of +memory files whose `MEMORY.md` was missing all 14 entries written after +2026-08-31 — copies drift silently because Claude Code only ever reads the live +path. Linking fixes the drift, but it also means that on any machine where the +live memory dir holds files not yet in the repo, `install` will delete them. + +**How to apply:** before running `install` on a machine whose Claude memory dir +is a real directory rather than a symlink, copy its `*.md` into +`common/claude/memory//` first and verify with `cmp`. The +sanitized name is the cwd with non-alphanumerics replaced by `-`, so it differs +per machine: `-home-connor` on mainframe, `-home-connorjohnstone` on sctfw004 +(user `connorjohnstone`). That is why the memory link lives in +`meta/hosts/mainframe.conf.yaml` only. + +Related: [[podman-compose-bind-mount-restart]] — the same shape of trap, where a +tool reports success without doing the thing you wanted. diff --git a/common/claude/memory/-home-connor/litellm-extra-body-reasoning.md b/common/claude/memory/-home-connor/litellm-extra-body-reasoning.md new file mode 100644 index 0000000..5be9c88 --- /dev/null +++ b/common/claude/memory/-home-connor/litellm-extra-body-reasoning.md @@ -0,0 +1,64 @@ +--- +name: litellm-extra-body-reasoning +description: "How to set reasoning effort per model through LiteLLM, and which routes are measured vs assumed" +metadata: + node_type: memory + type: reference + originSessionId: 20b564c4-d665-445f-a1c7-25f9e6e666c1 + modified: 2026-09-17T13:42:14.834Z +--- + +**GLM-5.3 (authoritative, from https://docs.z.ai/guides/capabilities/thinking):** + +- `reasoning_effort` is the control. **Default is `max`** — which means "think + until done", not "think a lot". An unset GLM will spend an unbounded number + of reasoning tokens on a large input, and that is documented behaviour, not a + fault. +- GLM-5.3 / 5.3-Flash accept **`max`, `high`, `low` ONLY**. Not `medium` — an + invalid value appears to fall back to the `max` default rather than erroring, + which looks exactly like a hang. +- ⚠ **GLM-5.3 cannot disable thinking.** `thinking: {type: "disabled"}` is + documented to error on 5.3/5.3-Flash. It is supported on 5.2 and earlier. + Measured against the coding-plan endpoint it returned HTTP 200 and cut + reasoning to 61 tokens anyway — an undocumented path with public reports of + being both silently ignored and erroring (code 1210). **Do not rely on it; + use `reasoning_effort`.** + +**gpt-oss-20b (local, llama.cpp):** `extra_body: {chat_template_kwargs: +{reasoning_effort: "low"}}` — 7,109 → 393 completion tokens, 148 s → 8 s on one +identical prompt. Plain `extra_body: {reasoning_effort: "low"}` also works but +cuts less deeply. Sent top-level it is a 400 `UnsupportedParamsError` naming +"openai", which is LiteLLM's adapter for a custom api_base, not the vendor. + +**opencode (agent tier), settled 2026-09-18.** opencode merges a model's +`options` into the request body VERBATIM — no camelCase translation, no AI-SDK +name mapping — so the value must be wrapped: + +```jsonc +"options": { "extra_body": { "reasoning_effort": "low" } } // works +"options": { "reasoning_effort": "low" } // silently inert +``` + +Proved binary with `max_tokens: 1`: flat and `extra_body`-wrapped both gave +`finish_reason: "length"`; `maxOutputTokens: 1` gave a normal reply. **There is +no model-level `body` field** — opencode 1.18.25's schema has options, headers, +limit, cost, reasoning, temperature, and no body. ⚠ Still unexplained: a flat +`reasoning_effort` should have 400'd and returned 200 instead. + +**What is measured vs assumed.** `extra_body` is a client-side mechanism +(OpenAI SDK / LiteLLM) for putting arbitrary fields in the request body; z.ai +itself documents these as ordinary top-level body fields, so the need for +`extra_body` is a LiteLLM routing fact, not a vendor one. Confirmed by +measurement for `thinking` on GLM (500 top-level), `reasoning_effort` on +gpt-oss (400 top-level), and — tested 2026-09-18, streaming AND non-streaming — +`reasoning_effort` top-level to GLM: **400**, `litellm.UnsupportedParamsError: +zai does not support parameters`. LiteLLM does NOT route it natively. + +⚠ An `extra_body` 200 proves less than an ordinary 200. `drop_params: false` +makes an unknown *top-level* parameter a loud 400, but an unrecognised key +nested in `extra_body` is ignored and still returns 200 — so a knob can be set +"successfully" and do nothing. Prove effect from token counts, never the status +code. + +Related: [[reasoning-tokens-vs-completion-tokens]]. +Details: `~/guides/llm/robots.md` §19, `~/docs/projects/llmbench/TRAPS.md`. diff --git a/common/claude/memory/-home-connor/llamacpp-offload-method.md b/common/claude/memory/-home-connor/llamacpp-offload-method.md new file mode 100644 index 0000000..3bcc17f --- /dev/null +++ b/common/claude/memory/-home-connor/llamacpp-offload-method.md @@ -0,0 +1,48 @@ +--- +name: llamacpp-offload-method +description: -ngl alone is the wrong knob for partial offload; full offload is a cliff, and -ot/KV-quant/MTP are the levers llmbench never used +metadata: + type: project +--- + +**Tuning `-ngl` alone is the naive method, and every speed number recorded on +mainframe before 2026-09-28 used it.** `llmbench`'s `ctxfit` ladders `-ngl` +and nothing else, so each recorded value is the optimum of the wrong +parameter. + +**Full offload is a CLIFF, not a slope.** Qwen3.8-27B, 65 blocks, measured +2026-09-28 (short/medium/long t/s): + + UD-Q4_K_XL 18.0 GB -ngl 36 6.58 / 5.44 / 3.20 + GSQ IQ3_S 11.8 GB -ngl 56 6.67 / 5.45 / 3.34 + GSQ IQ3_XXS 9.8 GB -ngl 99 13.31 / 11.75 / 8.52 + +Going 36 -> 56 layers resident bought ~0%. Going 56 -> 65 (all of them) +bought **+155%** at long context. With ANY layer on CPU every token pays a +PCIe round trip; removing the last one removes the whole penalty rather than +a proportional share. A prediction of ~35% from a linear "3.5x per CPU layer" +model was badly wrong. ttft collapsed too: 24,255 ms -> 313 ms warm. + +**Three levers llama.cpp offers that were never used here:** + +- `--override-tensor "blk\.(...)\.ffn_.*=CPU"` with `-ngl 99`. Spills only + FFN tensors, which are large and latency-tolerant, keeping ALL attention + resident -- attention is small and used constantly. `-ngl N` instead spills + whole blocks, attention included. This is the right knob whenever a model + does NOT fully fit. +- `-ctk q4_0 -ctv q4_0` quantized KV. Once weights fit, this buys CONTEXT + rather than speed. +- `--spec-type draft-mtp --spec-draft-n-max 2` with a `-mtp` GGUF. Reported + 88% acceptance, 2.76 mean accepted tokens -- near 2x decode, independent of + offload. Both Qwen3.8's MTP head and Glimmer's DFlash drafter sat unused. + +**Why:** a public report runs Qwen3.8-27B at **~9.7 t/s with 96K context on +the same RTX 3060 12GB using Q4_K_S** -- a LARGER quant than ones dismissed +here as too slow. "Too big to be fast" was never established; only "too big +to be fast when you spill whole layers". + +**How to apply:** when a model does not fully fit, use `-ngl 99` plus an +`-ot` FFN regex and tune the block count, rather than lowering `-ngl`. Re-test +anything previously disqualified on speed -- the verdict may be an artifact of +the method. `ctxfit` needs to search FFN placement, not just `-ngl`. +See [[npu-setting-clamps-memory]]. diff --git a/common/claude/memory/-home-connor/local-code-autocomplete.md b/common/claude/memory/-home-connor/local-code-autocomplete.md new file mode 100644 index 0000000..140ceb0 --- /dev/null +++ b/common/claude/memory/-home-connor/local-code-autocomplete.md @@ -0,0 +1,71 @@ +--- +name: local-code-autocomplete +description: "Local code autocomplete (FIM) works on mainframe; qwen3-coder-30b is the best model for it despite being written off for capacity" +metadata: + type: reference +--- + +Measured 2026-09-18 (llmbench suite 6b, `fim`). + +**Local code autocomplete is viable on this box.** Route: a raw +`/v1/completions` through LiteLLM with the model's FIM control tokens in the +prompt. ⚠ **`/infill` is 404 through the proxy** — not exposed. + +**FIM is a vocabulary feature, so only two local models can do it at all:** + +| model | FIM tokens | exact | TTFT warm | +|---|---|--:|--:| +| `qwen3-coder-30b` | yes | **0.56** | **62 ms** | +| `qwen3-next-80b-128k` | yes | 0.25 | 149 ms | +| `gpt-oss-20b`, `gemma3-12b` | **none** | — | — | + +⚠ **Check the GGUF VOCAB, not the metadata keys.** None of these four declare +`tokenizer.ggml.fim_*_token_id`; the tokens are in the vocab regardless. A +metadata check says "no local model can infill" and is a false negative. + +⚠ **`qwen3-coder-30b`'s "SKIPPED FOR CAPACITY" verdict does not apply here.** +That was recorded against the agent tier, where 128k is the floor. +Autocomplete needs a few thousand tokens around the cursor. The verdict was +nearly inherited without checking its scope. + +⚠ **Exact match is a FLOOR.** Of its four misses, one is correct code (a valid +`match` arm both models independently prefer over upstream's) and one is +defensible. Read completions before quoting the number: +`uv run python tools/inspect_fim.py --only differs`. + +Open question is not capability but **eviction**: a local autocomplete tier +fires per keystroke and llama-swap runs one llama-server — see +[[podman-compose-bind-mount-restart]] context and `robots.md` §11. + +Related: [[litellm-extra-body-reasoning]] for how parameters reach the model. + +--- + +**2026-09-29 — the failure mode to check first, and a tuning trap.** + +Two independent things broke autocomplete on the same day; symptoms are +identical (no ghost text), so check them in this order. + +1. **`rbw` locked → minuet never sends a request at all.** There is no + `LITELLM_KEY_NVIM` in `~/.secrets.zsh`; the key comes from `rbw` only, and + minuet caches "no key" for the whole nvim session. Tell-tale: ZERO requests + in the llama-swap and LiteLLM logs, not slow ones. Confirm in one command: + `nvim --headless +MinuetStatus +qa` prints `key: NOT FOUND`. Fix: `rbw + unlock`, then restart nvim. + +2. ⚠ **Do not tune qwen3-coder-30b for decode throughput.** A `servefit sweep` + set `-b 256 -ub 128` for +21% decode. Its only consumer is FIM autocomplete, + which emits 32 tokens but prefills the whole cursor context — **TTFT is + prefill-bound and decode is nearly irrelevant.** Small batches cut prefill + from ~1150 to ~445 tok/s, putting a 3.7k-token context at 5.1 s against + minuet's 3 s `request_timeout`: every completion aborted client-side. + Restored to `-b 2048 -ub 512` (keeping `-ncmoe 20` and q4_0 KV — the A/B + isolated `-b`/`-ub`). Costs ~92 MiB of VRAM headroom, 541 → 449 MiB spare. + + The sweep was not wrong, it was **scoped to the wrong metric** — the same + error shape as inheriting this model's "skipped for capacity" verdict above. + Benchmark the role, not the model. + +Warm completions measure 0.98 s, matching the 0.7-1.3 s on record. A cold +first request on a large file is still ~3.8 s and will exceed the 3 s timeout; +raising `request_timeout` in `common/nvim/init.lua` is the open option. diff --git a/common/claude/memory/-home-connor/mainframe-bad-core.md b/common/claude/memory/-home-connor/mainframe-bad-core.md new file mode 100644 index 0000000..e1ab334 --- /dev/null +++ b/common/claude/memory/-home-connor/mainframe-bad-core.md @@ -0,0 +1,39 @@ +--- +name: mainframe-bad-core +description: mainframe has one E-core throwing fatal MCEs; it is offlined by a systemd unit and is an RMA candidate +metadata: + type: project +--- + +mainframe's CPU (Core Ultra 9 285K) has **one bad core: APIC 20**, which is +cpu10, an E-core. Three FATAL machine checks in six days, all on that APIC: + + 2026-09-16 12:55 Bank 0 f2000000000b0005 MCA 0x0005 internal parity + 2026-09-21 22:15 Bank 3 be000000e1c40400 MCA 0x0400 internal unclass. + 2026-09-22 18:49 Bank 3 be000000e1c40400 (byte-identical to previous) + +All carry UC + PCC, so the platform resets instantly -- the journal stops +mid-line with no shutdown or panic. Each is recovered from firmware's BERT +table on the next boot. + +**Why it is the core and not the config:** the first MCE predates the +2026-09-21 XMP + aggressive-CPU-profile change by five days. Memory is +exonerated (core-internal banks, empty EDAC, ADDR holding a userspace VIRTUAL +address identical to MISC). Microcode was already current (0x122). BIOS was +updated 1.A20 -> 1.AE0 on 2026-09-22 to remove two-year-old firmware as a +confound before any RMA. + +⚠ The "CPU n" in an MCE line is whichever CPU logged it, not the faulting +core -- all three say "CPU 2" and mean APIC 20. Read the APIC field. And the +APIC->cpu mapping is not guaranteed stable across boots, so resolve it at +runtime rather than hardcoding cpu10. + +**How to apply:** `~/config/host/offline-bad-core.sh` offlines it and installs +`offline-bad-core.service` to do so every boot; `--check` prints state plus +per-boot MCE counts. Connor wants it left OFF until he is back from +California (2026-09-22), because a crash while away is worse than the +diagnostic. Re-enabling it on the new BIOS is the test that decides RMA: +if APIC 20 faults again on current firmware it is a defective part, and the +285K carries Intel's 3-year boxed warranty. Inference is pinned to P-cores +(cpuset 0-7, `taskset -c 0-4`), so losing this E-core costs nothing. +See [[npu-setting-clamps-memory]]. diff --git a/common/claude/memory/-home-connor/nas-dual-homed-wifi-path.md b/common/claude/memory/-home-connor/nas-dual-homed-wifi-path.md new file mode 100644 index 0000000..3132ec2 --- /dev/null +++ b/common/claude/memory/-home-connor/nas-dual-homed-wifi-path.md @@ -0,0 +1,48 @@ +--- +name: nas-dual-homed-wifi-path +description: NAS is dual-homed on 10.0.0.0/24 and all its traffic silently used WiFi instead of the 2.5GbE wire; this was the real cause of slow NFS +metadata: + type: project +--- + +The NAS has both interfaces live on the SAME subnet: + + enp3s0 10.0.0.3/24 wired, 2500 Mb/s full duplex + wlo1 10.0.0.42/24 WiFi, about -61 dBm + +Two independent faults made all NAS traffic take the WiFi path: + +1. Forward (mainframe -> NAS). With `arp_ignore=0` the NAS answered ARP for + 10.0.0.3 from wlo1, so mainframe cached `10.0.0.3 -> f8:3d:c6:2f:b6:68`, + the WiFi MAC. Packets addressed to the wired IP were delivered over WiFi. + Wired MAC is `a0:ad:9f:88:d5:fa` — compare against `ip neigh show 10.0.0.3`. +2. Return (NAS -> mainframe). NetworkManager installed NO 10.0.0.0/24 route + for enp3s0 at all, only `dev wlo1 ... metric 600`, so every reply went out + over WiFi regardless of how the request arrived. + +Fixing only one of the two barely helps; both must be right. Measured on +2026-10-02, small-file creates over NFS: + + WiFi path (both wrong) 456 ms/file, ~4 MB/s + ARP fixed only 330 ms/file + both directions wired 60 ms/file, ~38 MB/s + +Runtime fix applied (NOT persisted — lost on NAS reboot or NM restart): +`sysctl -w net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2` +and `ip route replace 10.0.0.0/24 dev enp3s0 proto kernel scope link src +10.0.0.3 metric 100`. Persisting it means an NM `ipv4.route-metric` on the +wired profile below wifi's 600, plus the sysctls in `/etc/sysctl.d/`. The +cleanest answer is probably to stop the stationary NAS joining WiFi at all. + +Even fully wired, NFS is ~60 ms per small-file create against an SSD's ~0.01 ms. +The array is LVM RAID1 over a Seagate ST8000DM004, which is SMR, so small +synchronous writes stay slow; local dd on the NAS gets 169 MB/s sequential. +Do not put build trees or git working copies on it. + +CORRECTION to [[nfs-pnfs-scsi-layout-wedge]]: the pNFS SCSI fence failure was +real and did wedge the mount, but it was not the cause of the chronic +slowness — it was most likely a consequence of timeouts over the flaky WiFi +link. The original reasoning was unsound because the pNFS fault was found +first and fully explained the symptoms in front of me, so I stopped looking; +the giveaway I initially read past was ping to the NAS swinging 0.5 ms to +180 ms, which no amount of NFS state can cause. diff --git a/common/claude/memory/-home-connor/nfs-mountpoints-immutable.md b/common/claude/memory/-home-connor/nfs-mountpoints-immutable.md new file mode 100644 index 0000000..9db027e --- /dev/null +++ b/common/claude/memory/-home-connor/nfs-mountpoints-immutable.md @@ -0,0 +1,49 @@ +--- +name: nfs-mountpoints-immutable +description: The seven NAS mountpoint dirs are chattr +i so services cannot write into them when NFS is down; use chattr -i to modify one +metadata: + type: project +--- + +Every NAS mountpoint directory on mainframe carries the ext4 immutable flag: + + /home/connor/{docs,books,audio,photo,video,downloads} and /mnt/nas + +Set 2026-10-02. **If you see `Operation not permitted` writing to one of these +paths, check whether NFS is actually mounted before doing anything else.** That +error is the feature working, not a bug. + +Why. When NFS is not mounted the mountpoint is just an empty local directory and +every service happily writes into it. The data lands on local disk, is hidden the +moment the mount returns, and nobody notices. By 2026-10-02 that had silently +accumulated 106 GB across `downloads` (86 G) and `video` (20 G) going back to +Oct 2025, plus Viseron NVR footage and transmission downloads. With `+i` those +writes fail immediately and loudly instead. + +Verified behavior (tested, not assumed): + + - write to unmounted mountpoint -> EPERM, even as root + - `mount` onto an immutable dir -> works + - `umount` -> works + - writes to the mounted filesystem -> entirely normal + +The flag lives on the UNDERLYING directory, which is covered while NFS is +mounted. To inspect or change it without unmounting, reach the real inode +through a bind mount: + + sudo mount --bind /home /mnt/under + lsattr -d /mnt/under/connor/docs + sudo chattr -i /mnt/under/connor/docs # to undo + sudo umount /mnt/under + +`/mnt/nas` lives on `/`, not `/home`, so it needs `mount --bind / /mnt/rootbind` +instead. The flag is stored in the inode and survives reboot; there is no unit +or script behind it. + +Connor declined the other two layers considered (systemd `RequiresMountsFor=` +and mount alerting) on 2026-10-02 — do not add them without being asked. +Scoping mattered for the systemd one: applying it to `podman-restart.service` +wholesale would take the whole stack down on a NAS-down boot, including gitea, +bitwarden, authelia and litellm, none of which touch NFS. + +Related: [[nas-dual-homed-wifi-path]], [[nfs-root-squash-loses-mtimes]] diff --git a/common/claude/memory/-home-connor/nfs-pnfs-scsi-layout-wedge.md b/common/claude/memory/-home-connor/nfs-pnfs-scsi-layout-wedge.md new file mode 100644 index 0000000..a2edc21 --- /dev/null +++ b/common/claude/memory/-home-connor/nfs-pnfs-scsi-layout-wedge.md @@ -0,0 +1,47 @@ +--- +name: nfs-pnfs-scsi-layout-wedge +description: NAS NFS writes wedging to a crawl is pNFS SCSI layouts failing to fence on an LVM RAID1 target; fix is blacklisting blocklayoutdriver on the client +metadata: + type: project +--- + +Symptom seen 2026-10-02: every write to the NAS mounts crawls or stalls for +60s+, reads stay fast, and the box bogs down. `/proc/meminfo` showed 8.5 GB +Dirty pinned on the NFS bdi with `BdiWriteBandwidth: 28 kBps`, and processes +stuck in D state on `balance_dirty_pages` / `vfs_unlink`. + +Cause chain. The NAS exports XFS on `/dev/mapper/raid-main` (dm-4), an LVM +RAID1 target. Linux nfsd automatically offers pNFS SCSI layouts for XFS, and +the client accepts — `/proc/self/mountstats` showed `pnfs=LAYOUT_SCSI`. +Fencing a SCSI layout uses SCSI persistent reservations, which the dm RAID1 +target does not implement, so the recall can never succeed: + + nfsd4_layout_fence_worker: FENCE failed client[10.0.0.2:938] clid[13] device[dm-4] + +The client lease then expires (`lease_expired` climbing, 90s lease) with dirty +pages pinned to a layout that will never be released. + +There is no SAN here — mainframe reaches the NAS over plain Ethernet and could +never use a SCSI layout at all. Fix applied on mainframe: +`/etc/modprobe.d/nfs-no-scsi-layout.conf` blacklisting `blocklayoutdriver`, +which implements both block and SCSI layouts. Without it the client does +ordinary NFS I/O over the wire. + +Recovery is NOT possible without rebooting the NAS once it wedges. Both +`echo expire > /proc/fs/nfsd/clients//ctl` and `systemctl restart +nfs-server` hang in D state, because each has to release the same unfenceable +layout; the restart leaves nfs-server stuck in `deactivating/final-sigterm` +and the NAS then cannot write to its own `/nas` either. + +Diagnosis commands worth reusing: `sudo cat /sys/kernel/debug/bdi/0:58/stats` +(all seven NAS mounts share bdi 0:58, so one fault stalls every export), +`/proc/fs/nfsd/clients/*/info` and `/states` on the NAS for stale client +records, and `grep -o 'lease_expired=[0-9]*' /proc/self/mountstats`. + +Related: [[podman-compose-bind-mount-restart]] + +CORRECTION (same day): this fault was real and did wedge the mount, but it was +NOT the cause of the chronic slow NFS writes. That was the NAS routing all its +traffic over WiFi — see [[nas-dual-homed-wifi-path]]. The layout fence most +likely failed because of timeouts over that flaky link. Keep the +blocklayoutdriver blacklist regardless; SCSI layouts can never work here. diff --git a/common/claude/memory/-home-connor/nfs-root-squash-loses-mtimes.md b/common/claude/memory/-home-connor/nfs-root-squash-loses-mtimes.md new file mode 100644 index 0000000..28f7041 --- /dev/null +++ b/common/claude/memory/-home-connor/nfs-root-squash-loses-mtimes.md @@ -0,0 +1,35 @@ +--- +name: nfs-root-squash-loses-mtimes +description: sudo cp -a onto the NAS docs/video/etc exports silently loses mtimes because only /nas has no_root_squash; copy as connor instead +metadata: + type: project +--- + +Copying with `sudo cp -a` onto any of the per-directory NAS exports +(`/nas/docs`, `/nas/video`, …) silently discards the original mtimes and stamps +every file with the copy time. `cp` reports success and the file contents are +correct, so it is easy to miss. + +Cause. `/etc/exports` on the NAS gives `no_root_squash` to `/nas` ONLY: + + /nas 10.0.0.0/28(rw,fsid=root,no_root_squash) + /nas/docs 10.0.0.0/28(rw,async) + +So on `/nas/docs` the client's root is squashed to anonymous. POSIX allows +setting an *arbitrary* mtime only to the file's owner or CAP_FOWNER; setting it +to *now* needs only write permission. Squashed root has the latter and not the +former, so nfsd downgrades the SETATTR and writes the current time. A root-owned +file on that export cannot have its mtime set from the client at all. + +The tell: the client reads back the correct mtime from its attribute cache while +the server has the wrong one. Always confirm with +`ssh nas stat -c %y /nas/docs/...` — the client's view will lie to you. + +Fix, in order of preference: +1. Copy as `connor`, not with sudo. The new files are connor-owned, and connor + can then set arbitrary mtimes on them. +2. If the files are already root-owned, `sudo chown -R connor:connor` ON THE NAS + over ssh (the client cannot chown there either), then set mtimes as connor. + +Observed 2026-10-02 archiving 20,116 viseron files; the first pass "fixed 19,924 +mtimes" and persisted none of them. Related: [[nas-dual-homed-wifi-path]]. diff --git a/common/claude/memory/-home-connor/npu-setting-clamps-memory.md b/common/claude/memory/-home-connor/npu-setting-clamps-memory.md new file mode 100644 index 0000000..4e6e481 --- /dev/null +++ b/common/claude/memory/-home-connor/npu-setting-clamps-memory.md @@ -0,0 +1,30 @@ +--- +name: npu-setting-clamps-memory +description: MSI BIOS NPU/AI options clamp DDR5 to 5/6 of XMP on mainframe, and the NPU is unusable by the inference stack anyway +metadata: + type: project +--- + +On mainframe (MSI PRO Z890-P WIFI, Core Ultra 9 285K), **enabling the BIOS +NPU / "AI performance" options clamps memory to 5333 MT/s even with XMP +Profile 1 correctly selected and reporting 6400.** Turning them off restores +6400. Measured 2026-09-22, both directions. + +**Why:** 5333 is exactly `6400 x 5/6`, and is NOT a JEDEC bin (those are +multiples of 400: 4800/5200/5600/6000/6400). A failed memory training falls +back to a JEDEC bin -- this box fell back to 5200 pre-XMP. A non-JEDEC +fraction of the target means a ratio is being imposed, not a fallback taken. +Arrow Lake's NPU sits on the SoC tile with the memory controller, so an +SoC-side ratio change reaches DRAM. Cost: 102.4 -> 85.3 GB/s, a 16.7% loss of +the bandwidth local inference is bound by. + +**The setting is strictly negative here despite its marketing.** llama.cpp in +this stack ships `libggml-cuda.so` plus CPU variants and has NO OpenVINO, +SYCL, Level Zero or NPU backend; `intel_vpu` loads with zero users. There is +no code path that can dispatch to the NPU, so it contributes nothing while +taking memory bandwidth. + +**How to apply:** leave the NPU/AI BIOS options OFF. If DRAM ever reads a +non-JEDEC speed, suspect an imposed ratio (check SoC/NPU/fabric settings) +rather than memory instability. `llmbench host-state` records ram_mts and +xmp_enabled every run, so the history is queryable. See [[mainframe-bad-core]]. diff --git a/common/claude/memory/-home-connor/owui-image-generation.md b/common/claude/memory/-home-connor/owui-image-generation.md new file mode 100644 index 0000000..7893573 --- /dev/null +++ b/common/claude/memory/-home-connor/owui-image-generation.md @@ -0,0 +1,31 @@ +--- +name: owui-image-generation +description: "Image generation through LiteLLM works, and Open WebUI's default 512x512 size is rejected by every image model" +metadata: + type: reference +--- + +Wired and verified 2026-09-18: Open WebUI → LiteLLM → OpenAI on +`openai/gpt-image-2`, tested with the `open-webui` virtual key rather than the +master key. + +⚠ **Open WebUI ships `image_generation.size` as `512x512` and EVERY image model +on this account rejects it.** Same key, same minute: + +- `gpt-image-1` — 400, *"Supported sizes are 1024x1024, 1024x1536, 1536x1024, + and auto."* +- `gpt-image-2`, `gpt-image-2.5-flare` — 400, *"below the current minimum pixel + budget"* + +`1024x1024` works. So a correct LiteLLM entry, a correct key allowlist and a +correct engine still give a feature that fails on every generation, surfaced in +the UI as a generic error. **The default is the trap.** + +Four changes are all required and the third is silent: a LiteLLM entry with +`model_info.mode: image_generation` (NOT `extra_body: {store: false}` — that is +a Responses-API field); `sudo podman restart connor_litellm_1`; `POST +/key/update` on the `open-webui` key; and the Open WebUI config rows via +`~/config/open-webui/apply-imagegen.py` with the container stopped. + +Related: [[podman-compose-bind-mount-restart]]. +Details: `~/guides/llm/robots.md` §20. diff --git a/common/claude/memory/-home-connor/pattern-kill-matches-wrong-process.md b/common/claude/memory/-home-connor/pattern-kill-matches-wrong-process.md new file mode 100644 index 0000000..bca5238 --- /dev/null +++ b/common/claude/memory/-home-connor/pattern-kill-matches-wrong-process.md @@ -0,0 +1,35 @@ +--- +name: pattern-kill-matches-wrong-process +description: pkill/pgrep/kill %n by pattern repeatedly hit the wrong process or missed the right one; capture PIDs instead +metadata: + type: feedback +--- + +**Do not manage processes by pattern.** Capture the PID when starting +something and act on that PID. `pgrep -f`, `pkill -f` and `kill %n` have gone +wrong repeatedly across sessions, in every direction: + +- `pgrep -f "podman image prune"` matched the monitor's own command line, so + it waited forever on itself. Connor: "I really don't understand why it's + taking this long." +- `pgrep -f "llmbench.cli run"` matched his `tmux new -As llmbench` session + and reported a runner that had finished hours earlier. Eight hours of idle + GPU followed, because the benchmark was reported as running when it was not. +- `hf download ... | head -5 & ... kill %1` killed the shell job but not the + `hf` child, which kept pulling the WHOLE repo -- 69 GB into + `~/.cache/huggingface` before it was spotted in a process listing. +- `pkill -f "llama-server.*9199"` killed the shell that was running the + heredoc writing the next script, so the script silently never existed. + +**Why:** a pattern matches the literal command line of EVERY process, +including the shell running the command that contains the pattern, monitors +built from it, tmux sessions named after the project, and editors with the +path open. It also does not follow pipelines or reach children a job-control +kill does not own. + +**How to apply:** save `$!` (or the pid from `ps`) and kill that. Where a +pattern is unavoidable, exclude self (`grep -v $$`), print what matched before +killing it, and VERIFY afterwards with `ps -p ` rather than assuming the +kill worked -- several of these failures were "I killed it" followed by the +process still running. To check whether work finished, prefer the durable +record (a run's `ended_at` in the database) over process presence. diff --git a/common/claude/memory/-home-connor/podman-compose-bind-mount-restart.md b/common/claude/memory/-home-connor/podman-compose-bind-mount-restart.md index 41302b9..e07e63f 100644 --- a/common/claude/memory/-home-connor/podman-compose-bind-mount-restart.md +++ b/common/claude/memory/-home-connor/podman-compose-bind-mount-restart.md @@ -28,3 +28,11 @@ within a second or two from the pre-existing state. llama-swap is the exception: it polls its own config every 2s and reloads itself. Related: [[zen-tos-hold]] + +**Single-file bind mounts also go stale when the file is replaced.** `./Caddyfile` +is mounted as one file. Claude's Edit tool (and any atomic-save editor) writes a +new inode, so the container keeps the old file: `caddy reload` succeeded and +loaded the OLD config, and the new site gave ERR_SSL_PROTOCOL_ERROR (no matching +site). Happened 2026-10-20 adding bazarr. Check with +`stat -c %i ~/Caddyfile` vs `sudo podman exec connor_caddy_1 stat -c %i /etc/caddy/Caddyfile`; +a `podman restart` re-attaches the mount. Directory mounts are not affected. diff --git a/common/claude/memory/-home-connor/podman-nfs-bind-rprivate-blind.md b/common/claude/memory/-home-connor/podman-nfs-bind-rprivate-blind.md new file mode 100644 index 0000000..9a6219d --- /dev/null +++ b/common/claude/memory/-home-connor/podman-nfs-bind-rprivate-blind.md @@ -0,0 +1,68 @@ +--- +name: podman-nfs-bind-rprivate-blind +description: "Containers bind NFS paths rprivate, so a mount landing after container start leaves them permanently blind while podman ps looks fine; fix is podman restart" +metadata: + node_type: memory + type: project + originSessionId: 744ca9f1-a40a-422d-9fdb-4c28998f8f25 + modified: 2026-10-03T02:09:18.003Z +--- + +The eleven containers that bind NAS paths (`jellyfin`, `jellyseerr`, `sonarr`, +`radarr`, `lidarr`, `whisparr`, `prowlarr`, `transmission`, `sftpgo`, +`syncthing`, `lazylibrarian`) declare plain binds in `compose.yml`: + + - /home/connor/video:/video + +With no propagation flag podman defaults to `rprivate`. A bind captures the +directory as it exists at bind time, so **if the NFS mount lands after the +container starts, the container holds the empty pre-mount directory forever.** +Nothing re-syncs it and nothing reports it. + +Measured 2026-10-02: + + 11:06:10 boot + 11:06:22 home-connor-video.mount FAILED (mount.nfs: mount system call + failed, status=32 -- NAS not ready) + 11:06:23 podman-restart.service started all containers -> empty bind + 11:38:01 mount retried -> failed again + 11:41:22 mount succeeded; no container ever saw it + +Ten hours. Jellyfin playback failed with `Error opening input: No such file or +directory` on every title, which reads as a per-file or per-client problem — the +report that opened the session was "Chromecast won't play Psych". It was neither +Chromecast nor Psych: all 24 bind mounts across all 11 containers were blind. +Sonarr logged 376 errors, all failed imports, no deletions. + +**Why:** `podman ps` says `Up`, healthchecks pass, the host path is correct and +fully populated, and `ls /home/connor/video` from a shell looks perfect. The +breakage is only visible from inside the container's mount namespace. + +**How to apply:** compare host and container counts rather than trusting either +alone, using each container's REAL bind destination — they differ +(`sonarr` is `/mnt/nas -> /nas`, `transmission` is `downloads -> /data`, +`sftpgo` is `/srv/sftpgo/data/connor/*`). Checking a guessed path returns 0 for +a directory that does not exist and fakes the symptom: + + sudo podman inspect --format '{{range .Mounts}}{{.Source}} -> {{.Destination}} + {{end}}' + sudo podman exec sh -c 'ls -A | wc -l' + +Fix is `sudo podman restart ` — the bind re-resolves against the live +mount. No compose change needed, and `podman-compose up -d` is not required +because the definition has not changed. + +Tested, not assumed: a tmpfs mounted onto a shared host path after container +start is seen by an `rslave` bind (1 entry) and NOT by an `rprivate` bind +(0 entries). So `:rslave` on these binds would make containers follow late +mounts and remounts, including the NAS-reboot case in +[[nfs-pnfs-scsi-layout-wedge]]. **Connor declined this on 2026-10-02 along with +the boot-mount retry — do not add either without being asked.** This is the +same scoping call recorded in [[nfs-mountpoints-immutable]]. + +Unrelated but found in the same pass: an 847 MB file named +`Taskmaster S22E05 Youre Nice 1080p WEB H264-NTb.exe` in +`downloads/completed/tv-sonarr/`. Flagged to Connor as a malware-shaped name. + +Related: [[nfs-mountpoints-immutable]], [[podman-compose-bind-mount-restart]], +[[nfs-pnfs-scsi-layout-wedge]], [[nas-dual-homed-wifi-path]] diff --git a/common/claude/memory/-home-connor/reasoning-tokens-vs-completion-tokens.md b/common/claude/memory/-home-connor/reasoning-tokens-vs-completion-tokens.md new file mode 100644 index 0000000..7bb8743 --- /dev/null +++ b/common/claude/memory/-home-connor/reasoning-tokens-vs-completion-tokens.md @@ -0,0 +1,38 @@ +--- +name: reasoning-tokens-vs-completion-tokens +description: "Reasoning tokens come out of max_tokens, and providers disagree about whether they count as completion tokens" +metadata: + node_type: memory + type: reference + originSessionId: 20b564c4-d665-445f-a1c7-25f9e6e666c1 + modified: 2026-09-17T13:35:03.190Z +--- + +Two facts that together produce an empty HTTP 200 that looks like a model +saying nothing: + +1. **Reasoning tokens come out of `max_tokens`.** A cap set anywhere near the + size of the expected *answer* is spent entirely on thinking, and the request + returns no content. Already documented in `~/config/litellm/config.yaml`'s + OpenAI block ("seen at max_tokens 16 and again at 500 on gpt-5.1-codex-max, + which then worked unchanged at 4000"). + +2. **Providers disagree about whether reasoning counts as completion.** + `gpt-oss` via llama.cpp folds reasoning INTO `completion_tokens`; + `glm/glm-5.3` reports `completion_tokens: 0` beside + `completion_tokens_details.reasoning_tokens: 1023` for the same behaviour. + Reading only `completion_tokens` shows a model that generated nothing. + +`finish_reason == "length"` is the authoritative signal — check it before any +token arithmetic. Add `reasoning_tokens` to `completion_tokens` whenever +computing a rate or testing a budget; a decode figure divided by completion +tokens alone reports a model that emitted 1,023 reasoning tokens and 12 of +content as running at the rate of the 12. + +**Do not cap `max_tokens` to a budget in a benchmark.** The cap silently +becomes the thing being measured, and an empty 200 grades as "found nothing" — +which on a code-review suite's defect-free control arm scores *perfectly*. +Use a ceiling far above observed spend, record it in the result row, and treat +an attempt that hits it as unusable rather than as an answer. + +Related: [[litellm-extra-body-reasoning]] for turning the reasoning down. diff --git a/common/claude/settings.json b/common/claude/settings.json index fb02c6e..e76393c 100644 --- a/common/claude/settings.json +++ b/common/claude/settings.json @@ -2,12 +2,15 @@ "enabledPlugins": { "rust-analyzer-lsp@claude-plugins-official": true }, + "permissions": { + "defaultMode": "auto" + }, "tui": "fullscreen", "theme": "dark", "autoMode": { "soft_deny": [ "$defaults", - "Bash(podman rm:*) in /home/connor/docs/projects/runway \u2014 destructive container removal", + "Bash(podman rm:*) in /home/connor/projects/runway \u2014 destructive container removal", "Bash(rm -rf:*) targeting baikal test containers in crates/runway-caldav/tests/baikal", "Bash(podman rm -f:*) \u2014 destructive podman container removal, frequently used CLI in this environment", "Bash(rm -rf ~/.env*) \u2014 sensitive credential files (.env, .env.bak-103643) found in config scan" @@ -43,7 +46,7 @@ "**Sensitive remote targets**: any namespace, host, or container whose name carries `prod` or `production` as a whole word or name segment (hyphen/underscore/dot-delimited)", "### User-specific", "**Primary use of Claude Code**: software development (Rust/Leptos/Axum CalDAV client)", - "**Trusted repo**: /home/connor/docs/projects/runway (working directory) and its remote ssh://git.rcjohnstone.com:2222/connor/runway.git \u2014 visibility assumed private per above, so confidential material is fine to commit here but secrets are never cleared into it by visibility alone", + "**Trusted repo**: /home/connor/projects/runway (working directory) and its remote ssh://git.rcjohnstone.com:2222/connor/runway.git \u2014 visibility assumed private per above, so confidential material is fine to commit here but secrets are never cleared into it by visibility alone", "**Org-specific CLIs**: None configured", "routine podman/systemctl/journalctl operations under this project's local/self-hosted infra", "**Primary use of Claude Code**: personal/hobby systems administration \u2014 home-lab container management (podman/podman-compose), self-hosted services (rcjohnstone.com subdomains), and local system maintenance", diff --git a/common/claude/skills/rust-crate-release/SKILL.md b/common/claude/skills/rust-crate-release/SKILL.md new file mode 100644 index 0000000..bbae73d --- /dev/null +++ b/common/claude/skills/rust-crate-release/SKILL.md @@ -0,0 +1,46 @@ +--- +name: rust-crate-release +description: Release and benchmarking procedure for Connor's Rust crates (ordinary-diffeq, astrokit) — the branch/tag/publish sequence, and the criterion baseline method with mainframe's measured noise floor. Use when cutting a release, bumping a version, publishing to crates.io, or measuring whether a change cost performance. +--- + +# Releasing and benchmarking a Rust crate + +The standing rules that apply to every edit — one coherent change at a time, +zero warnings, `cargo fmt`, 0.x breaking changes are minor bumps, a Gitea issue +per feature — stay in `~/.claude/CLAUDE.md` under "Rust libraries". This file +holds the two procedures you only need when you are actually releasing or +benchmarking. + +## Benchmarking, when a change could plausibly cost something + +criterion, and measure rather than assert: + +- Baseline and change in one sitting: `git stash push -- src`, `cargo bench`, + `git stash pop`, `cargo bench` again. Criterion compares against the + baseline it just saved and prints the delta. +- Then run the identical binary once more to see the noise floor. On mainframe + that drift is around a percent, larger than most "no regression" claims, so + a 0.9% reading means nothing until you know it. +- Never run two cargo invocations at once. They fight over the target lock, + starve each other on a loaded box, and the timings come out worthless. +- Write down the measured numbers, not the conclusion drawn from them. + +## The release process + +Once the work itself is committed and pushed: + +```sh +git checkout -b release/0.6.0 +# bump version in Cargo.toml, nothing else +git commit -am "Release 0.6.0" # body: what changed, and what it breaks +git checkout main && git merge --no-ff release/0.6.0 +git tag -a v0.6.0 -m " 0.6.0 - " +git push origin main release/0.6.0 v0.6.0 +cargo package --list && cargo publish --dry-run && cargo publish +``` + +The release commit touches `Cargo.toml` alone, so the tag is the tree that was +tested. `cargo package --list` is the check that `exclude` is doing its job and +no scratch file is going out. These repos have no Gitea release entries — the +annotated tag is the release, and Gitea lists it under Tags. A publish cannot +be undone; a bad version can only be yanked. diff --git a/common/nvim/init.lua b/common/nvim/init.lua index 6a0cb71..4d509b3 100755 --- a/common/nvim/init.lua +++ b/common/nvim/init.lua @@ -179,6 +179,10 @@ minuet.setup({ -- Default is 'warn', which hides request failures. If completions stop -- appearing, set this to 'debug' and the reason shows up in `:messages`. notify = "warn", + -- Default 3. Measured 2026-10-22 at -ncmoe 22: a fresh ~3k-token context + -- takes 4.8 s to prefill, the same context again 0.8 s. At 3 s the first + -- completion in a file times out, and it may not leave a cache behind. + request_timeout = 8, -- Ghost text: there is no completion engine here (no nvim-cmp, no blink) -- and virtual text is the copilot-shaped one. virtualtext = { diff --git a/common/qutebrowser/quickmarks b/common/qutebrowser/quickmarks index 70cf9df..7984aa8 100644 --- a/common/qutebrowser/quickmarks +++ b/common/qutebrowser/quickmarks @@ -65,7 +65,7 @@ aero https://filespace.aero.org/human.aspx?r=4292318470 whatsapp https://web.whatsapp.com/ sedaro_onedrive https://sedarocorp-my.sharepoint.us/personal/robbie_robertson_sedaro_com/_layouts/15/onedrive.aspx?id=%2Fpersonal%2Frobbie_robertson_sedaro_com%2FDocuments%2F00%20Sedaro%20Drive%2F06%20Contracts%2F15%20RROCI%2FDeliverables%2FOrion_Sedaro_Fileshare&ga=1 sedaro_sharepoint https://sedarocorp-my.sharepoint.us/personal/robbie_robertson_sedaro_com/_layouts/15/onedrive.aspx?id=%2Fpersonal%2Frobbie_robertson_sedaro_com%2FDocuments%2F00%20Sedaro%20Drive%2F06%20Contracts%2F15%20RROCI%2FDeliverables%2FOrion_Sedaro_Fileshare&ga=1 -jellyseerr https://jellyseerr.rcjohnstone.com/ +seerr https://seerr.rcjohnstone.com/ e https://192.168.1.101:8006/#v1:0:=qemu%2F101:4:::::8:: element https://element.rcjohnstone.com/ aws access portal https://start.us-gov-east-1.us-gov-home.awsapps.com/directory/d-c267218df8#/?tab=accounts diff --git a/hosts/mainframe/bin/stream b/hosts/mainframe/bin/stream index 6e7ddac..570a849 100755 --- a/hosts/mainframe/bin/stream +++ b/hosts/mainframe/bin/stream @@ -11,9 +11,14 @@ UNIT=sway-headless.service IDLE_MINUTES="${STREAM_IDLE_MINUTES:-15}" STAMP="${XDG_RUNTIME_DIR:-/tmp}/stream.lastseen" -# A Moonlight client holds connections to the control and RTSP ports. +# The TCP ports are only held while a session is being launched; once it is +# streaming, everything is UDP on unconnected sockets that ss cannot attribute +# to a peer. A live NVENC session is what shows a stream in progress. clients_connected() { - ss -Htn state established '( sport = :47984 or sport = :47989 or sport = :48010 )' 2>/dev/null | grep -q . + ss -Htn state established '( sport = :47984 or sport = :47989 or sport = :48010 )' 2>/dev/null | grep -q . && return 0 + local sessions + sessions=$(nvidia-smi --query-gpu=encoder.stats.sessionCount --format=csv,noheader,nounits 2>/dev/null | head -n1) + (( ${sessions:-0} > 0 )) } vram() { diff --git a/hosts/mainframe/sunshine/apps.json b/hosts/mainframe/sunshine/apps.json index 42bd3cb..e4bcba8 100644 --- a/hosts/mainframe/sunshine/apps.json +++ b/hosts/mainframe/sunshine/apps.json @@ -20,11 +20,43 @@ { "do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"", "undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz" + }, + { + "do": "swaymsg workspace steam", + "undo": "swaymsg workspace number 1" } ], "detached": [ "setsid steam -gamepadui" ] + }, + { + "name": "FreeCAD", + "prep-cmd": [ + { + "do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"", + "undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz" + }, + { + "do": "swaymsg workspace freecad", + "undo": "swaymsg workspace number 1" + } + ], + "cmd": "freecad" + }, + { + "name": "OrcaSlicer", + "prep-cmd": [ + { + "do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"", + "undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz" + }, + { + "do": "swaymsg workspace orcaslicer", + "undo": "swaymsg workspace number 1" + } + ], + "cmd": "env GDK_BACKEND=x11 WEBKIT_DISABLE_DMABUF_RENDERER=1 SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt orca-slicer" } ] } diff --git a/hosts/mainframe/sway/headless.conf b/hosts/mainframe/sway/headless.conf index 61714b1..ea4d51b 100644 --- a/hosts/mainframe/sway/headless.conf +++ b/hosts/mainframe/sway/headless.conf @@ -28,34 +28,117 @@ exec systemctl --user import-environment WAYLAND_DISPLAY DISPLAY SWAYSOCK XDG_CU # Input arrives from the Moonlight client, so keybinds are worth having even # though nobody is sitting at this machine. Without them the Desktop app # streams an empty screen with no way to open anything. -set $mod Mod4 +# +# Mod1 (Alt), not Mod4 (Super). The client machines run Hyprland with SUPER as +# their own modifier, and a compositor grabs its own modifier before Moonlight +# ever sees the key, so every Super bind here was unreachable from the stream — +# it acted on the client instead. Alt is not grabbed there, so it arrives. The +# cost is that an Alt bind here shadows the focused app's menu mnemonics +# (Alt+F, Alt+D), which is acceptable on a session that shows one fullscreen +# app at a time. +set $mod Mod1 -# Floating, not tiling. Nobody sits at this machine and nothing here is a -# workstation: the stream shows one thing at a time, usually Steam fullscreen. -# Tiling only ever meant a dialog box stealing half the screen. Both criteria -# are needed - app_id matches Wayland clients, class matches Xwayland ones, -# and Steam is an X client. -for_window [app_id=".*"] floating enable -for_window [class=".*"] floating enable +# Tiling, sway's default. This used to float every window, which left a +# single-app stream at whatever size the app opened at instead of filling the +# output. Each Sunshine app switches to its own workspace first (apps.json), so +# its window is alone there and fills the screen. Dialogs sway recognizes as +# dialogs still float. # Title bars are the handle you grab with the mouse, so floating windows keep # a real border rather than the thin default. default_floating_border normal 2 -# Super+drag moves a window, Super+right-drag resizes it. Without this the +# Alt+drag moves a window, Alt+right-drag resizes it. Without this the # only way to move a window is the title bar, which a fullscreen app has none of. floating_modifier $mod normal -# The stream forwards a mouse, so pointing and clicking is the primary way -# around; these are for when the pointer is not enough. +# Hyprland takes $browser from hosts//hypr/local-env.conf; mainframe links +# no hypr config, so it is named here. qutebrowser, to match the laptops. +set $browser qutebrowser + +# These mirror common/hypr/hyprland.conf, so the muscle memory from the laptops +# carries over the stream. Only binds whose target exists on this host are +# ported: mainframe has no grim/grimblast, no udiskie, no +# networkmanager_dmenu, no ydotool, no julia, no ncmpcpp and no swayimg, so +# Hyprland's $mainMod+P, Shift+P, M, W, A, S and V are deliberately absent +# rather than bound to a missing binary — sway reports nothing when an exec +# target does not exist, so a ported bind would look correct and silently do +# nothing. The brightness keys are out for the same reason twice over: +# brightnessctl is absent and /sys/class/backlight is empty. +# +# $mainMod+N and Shift+N are out too, less obviously. The daemon here is mako, +# not swaync, and mako's do-not-disturb is not built in — it needs a +# [mode=do-not-disturb] invisible=1 section, and this host has no mako config at +# all. `makoctl mode -t do-not-disturb` would therefore succeed and suppress +# nothing. Write that config first if these binds are wanted. +# +# $mainMod+Ctrl+L is not ported: there is no lock screen here on purpose, and +# locking the session Sunshine captures would blank the stream. + +bindsym $mod+Return exec kitty +bindsym $mod+Shift+Return exec wofi --show drun +bindsym $mod+b exec $browser +bindsym $mod+q kill +bindsym $mod+Shift+q exit +bindsym $mod+f fullscreen +bindsym $mod+t floating toggle + +# Hyprland moves focus with both vim keys and arrows; so does this. +bindsym $mod+h focus left +bindsym $mod+j focus down +bindsym $mod+k focus up +bindsym $mod+l focus right +bindsym $mod+Left focus left +bindsym $mod+Down focus down +bindsym $mod+Up focus up +bindsym $mod+Right focus right + +# Not a Hyprland bind, kept because a Moonlight client arrives with a pointer +# and little else: this is the only way to reach a window the pointer cannot +# see. Spelled literally rather than as $mod so it stays Alt+Tab if $mod moves. bindsym Alt+Tab focus next bindsym Alt+Shift+Tab focus prev -bindsym $mod+f fullscreen -bindsym $mod+Return exec kitty -bindsym $mod+d exec wofi --show drun -bindsym $mod+Shift+q kill -bindsym $mod+Shift+e exec swaymsg exit +# Workspaces, and $mod+scroll to cycle them, as on the laptops. Sway has no +# master layout, so Hyprland's $mainMod+Space, Y, U, O and I have no +# counterpart; nor do its +# movecurrentworkspacetomonitor binds, with one virtual output. +bindsym $mod+1 workspace number 1 +bindsym $mod+2 workspace number 2 +bindsym $mod+3 workspace number 3 +bindsym $mod+4 workspace number 4 +bindsym $mod+5 workspace number 5 +bindsym $mod+6 workspace number 6 +bindsym $mod+7 workspace number 7 +bindsym $mod+8 workspace number 8 +bindsym $mod+9 workspace number 9 +bindsym $mod+0 workspace number 10 + +bindsym $mod+Shift+1 move container to workspace number 1 +bindsym $mod+Shift+2 move container to workspace number 2 +bindsym $mod+Shift+3 move container to workspace number 3 +bindsym $mod+Shift+4 move container to workspace number 4 +bindsym $mod+Shift+5 move container to workspace number 5 +bindsym $mod+Shift+6 move container to workspace number 6 +bindsym $mod+Shift+7 move container to workspace number 7 +bindsym $mod+Shift+8 move container to workspace number 8 +bindsym $mod+Shift+9 move container to workspace number 9 +bindsym $mod+Shift+0 move container to workspace number 10 + +bindsym --whole-window $mod+button4 workspace prev_on_output +bindsym --whole-window $mod+button5 workspace next_on_output + +# Volume reaches the client: the default sink is sink-sunshine-stereo, which is +# the one Sunshine captures. --locked because there is no lock screen to respect. +# XF86AudioMicMute is not ported — this host has no audio source at all. +bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ +bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- +bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle + +bindsym --locked XF86AudioNext exec playerctl next +bindsym --locked XF86AudioPause exec playerctl play-pause +bindsym --locked XF86AudioPlay exec playerctl play-pause +bindsym --locked XF86AudioPrev exec playerctl previous # Something visible on login, so a working capture is obviously working rather # than indistinguishable from a broken one. diff --git a/meta/hosts/mainframe.conf.yaml b/meta/hosts/mainframe.conf.yaml index 4dd82d8..200bac3 100644 --- a/meta/hosts/mainframe.conf.yaml +++ b/meta/hosts/mainframe.conf.yaml @@ -28,6 +28,8 @@ ~/.config/opencode/local.json: common/opencode/local.json ~/.claude/CLAUDE.md: common/claude/CLAUDE.md ~/.claude/settings.json: common/claude/settings.json + ~/.claude/skills/rust-crate-release: common/claude/skills/rust-crate-release + ~/.claude/projects/-home-connor/memory: common/claude/memory/-home-connor ~/.config/sunshine/sunshine.conf: hosts/mainframe/sunshine/sunshine.conf ~/.config/sunshine/apps.json: hosts/mainframe/sunshine/apps.json ~/.config/systemd/user/inbox-tidy.service: hosts/mainframe/systemd/inbox-tidy.service diff --git a/meta/hosts/sctfw004.conf.yaml b/meta/hosts/sctfw004.conf.yaml index d3febea..8e8ed2a 100644 --- a/meta/hosts/sctfw004.conf.yaml +++ b/meta/hosts/sctfw004.conf.yaml @@ -52,6 +52,7 @@ ~/.config/opencode/work-strict.json: common/opencode/work-strict.json ~/.config/opencode/local.json: common/opencode/local.json ~/.claude/CLAUDE.md: common/claude/CLAUDE.md + ~/.claude/skills/rust-crate-release: common/claude/skills/rust-crate-release ~/.config/hypr/hyprland.conf: common/hypr/hyprland.conf ~/.config/hypr/hyprlock.conf: common/hypr/hyprlock.conf ~/.config/hypr/toggleFloating.sh: common/hypr/toggleFloating.sh diff --git a/packages/mainframe.aur b/packages/mainframe.aur index 57ee97f..02c9a59 100644 --- a/packages/mainframe.aur +++ b/packages/mainframe.aur @@ -4,6 +4,7 @@ claude-code llama.cpp-cuda nvidia-patch-git oh-my-zsh-git +orca-slicer-bin overskride-bin paru paru-debug diff --git a/packages/mainframe.pacman b/packages/mainframe.pacman index 97e666d..8ee6cda 100644 --- a/packages/mainframe.pacman +++ b/packages/mainframe.pacman @@ -11,8 +11,10 @@ cronie cuda decibels dhcpcd +difftastic dmidecode docker +dosfstools dust dysk efibootmgr @@ -21,10 +23,12 @@ ethtool eza fd fio +freecad fzf gcc gdm git +git-delta glow gnome-backgrounds gnome-calculator @@ -54,9 +58,11 @@ gnome-tour gnome-user-docs gnome-user-share gnome-weather +go go-yq gpicview grilo-plugins +grim grub gvfs gvfs-afc @@ -72,6 +78,7 @@ htop hyprland i3-wm intel-ucode +jdk21-openjdk jq kitty lazygit @@ -123,6 +130,7 @@ pacman-contrib pandoc-cli papers pcmanfm +perf pkgconf podman podman-compose @@ -130,6 +138,7 @@ python python-pip python-requests python-rich +python-weasyprint qutebrowser rbw restic