Skip to content

llm-agents 2026-09-23 (claude-code 2.1.280), drop the SessionEnd harvest hook, shut the coordinator's :8731 wifi door (#460) - #461

Merged
mecattaf merged 14 commits into
mainfrom
chore/llm-agents-bump-drop-harvest-hook
Sep 23, 2026
Merged

mecattaf merged 14 commits into
mainfrom
chore/llm-agents-bump-drop-harvest-hook

Conversation

@mecattaf

Copy link
Copy Markdown
Owner

Three independent changes, applied to the coordinator (generation 4vda6fvd…) and verified live.

1. 334e5964 — halogen: the LAN door follows autoStart, not enable (#460)

modules/halogen.nix opened port on lanInterface for every host setting services.halogen.enable. modules/strix.nix declares the server on both twins and splits only autoStart, so the coordinator — which by design serves no model — was admitting the unauthenticated :8731 API on wlp192s0, its wifi uplink.

The module's own "no authentication is fine, it is admitted on the LAN interface only; every client on that segment is a pinned house device" argument is true of the worker's wired enp191s0. It was never a claim about the coordinator's wifi segment.

New openLanPort (default autoStart) gates the firewall line. Declaring the server and serving the fleet are separate statements again.

flake.nix:1695 had asserted exactly this since 2026-09-16 and was failing, taking checks.nas-topology — and everything evaluated after it — down with it. The assert is unchanged; it was right all along.

2. a775d58f — remove the SessionEnd harvest hook

It blocked every session close; Claude Code reported the abort as SessionEnd hook [...] failed: Hook cancelled each time. The script was not at fault — it always exited 0 and timed itself out under the hook timeout. The shape is: the session process waits for a SessionEnd hook, and harvesting runs took up to 57 s.

Its own ledger is the argument: of the last 101 runs, 46 created a note, 1 updated one, 54 skipped.

ai_memory.py, the harvest verb, its --enqueue leg and MEM-3's probe all stay — drain still runs the verb on demand, so only the automatic leg is gone. checks.no-claude-code-hooks replaces the removed check and is strictly broader: it fails on any hook block in settings.json and on anything delivered into ~/.claude/hooks.

3. 3a658991 — llm-agents to 2026-09-23 HEAD

before after
claude-code 2.1.266 2.1.280
codex 0.153.4 0.155.1

Verified on the coordinator after switch

claude --version            2.1.280 (Claude Code)
codex --version             codex-cli 0.155.1
iptables -S | grep 8731     (gone from wlp192s0)
~/.claude/hooks/…harvest.sh No such file or directory
settings.json hooks         none
nix flake check             all checks passed!

Coordinator wlp192s0 drops to [80, 443]; worker enp191s0 keeps [3003, 8731].

Unrelated: the switch exited non-zero on fwupd-refresh.service (PolicyKit daemon is not available), a pre-existing failure independent of these changes. Not suppressed, not addressed here.

Closes #460.

🤖 Generated with Claude Code

mecattaf and others added 12 commits September 17, 2026 06:23
…0.96

Tom 2026-09-16: "let's make it the green pistachio recreated 5k monitor my
wallpaper", then "make the blur at 0.96 from the niri side for kitty
terminal, to appreciate the new colors. this is not transparency".

- dot_local/share/wallpapers/imagine-olive-5120x2880.png: the claude.ai/imagine
  "olive" theme (ground #7c8b62) — the exact recovered source SVG
  (~/colors/waves/capture/imagine-background.svg) rasterised by Chrome under
  Anthropic's own CSS recipe (cover/center) at the PA27JCV's native
  5120x2880. Not July's design-pass export, whose reconstruction has the
  crossing-ribbons bug (~/colors/waves/STATE.md).
- bin/wallpaper defaults to it; startup.kdl spawns `wallpaper` again (the
  swaybg spawn was deleted 2026-09-11 for the Zenbook's OLED black).
- window-rules.kdl: the kitty blur rule is back, now matching every
  kitty-derived app-id — kitty, herdr-projector, and the fzf *-prompt popups —
  so no kitty is see-through without the frost. kitty.conf: background_opacity
  0.96, dynamic_background_opacity yes. Same mechanism PR #381 removed: kitty
  makes only its background translucent, niri blurs the wallpaper behind it,
  text stays opaque.

Shared RAW files: the client laptop gets the wallpaper and the frost too after
its next pull.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…erdr, nvim, fish

Tom 2026-09-16: "resurface the light mode and the dark mode, original from
claude. currently i have 'noir' mode. i wonder what it would take to make a
dotfiles-wide switcher … i'm talking kitty, herdr(?), nvim. gtk-noir is fine
to keep it that way for now" — three hand-curated themes, switched in a
nix-native way. Design and option analysis: docs/theme-switcher-2026-09-17.md
(full research report in ~/colors/theme-switcher-design.md).

The themes. noir is the Claude Dark 12-role syntax palette on OLED black;
claude-dark is the same palette on claude.ai's own dark grounds (#1A1A1A
code-block, #20201F page) — home/themes/claude-dark.nix is `noir // {
ground; … }`; claude-light is the Claude Light palette on --bg-100 #F9F9F7,
with a designed 16-slot ANSI mapping (Claude ships none), every slot WCAG AA
on white, flagged for colorlab tuning. Sources: the palettes recovered from
the claude.ai bundle in ~/colors/waves/capture/claude-code-theme/.

The mechanism. home/themes/*.nix are palettes keyed by ROLE; default.nix
renders each app's colour FRAGMENT; home/theme.nix writes every fragment for
every theme under ~/.config/themes/<name>/ and bootstraps/heals the pointer
~/.config/theme, a plain symlink into themes/ that ~/.local/bin/theme
retargets at runtime (HM owns the plural, the switcher the singular). Every
RAW file keeps its body raw and joins its fragment through the app's own
include — the kitty-scrollback-nix.conf / niri-local.kdl move, one level up.
Switching needs no rebuild; only adding a theme or changing a palette does.

- kitty.conf: the colour block becomes `include ${HOME}/.config/theme/kitty.conf`;
  opacity stays raw (dynamic_ cannot change on reload). `theme` runs
  `kitten @ load-config` per instance socket — a symlink retarget fires no
  watcher event, so the explicit reloads are load-bearing.
- niri: config.kdl includes ~/.config/theme/niri.kdl LAST (sections merge,
  later wins per property; `optional=true` so a checkout pulled ahead of its
  switch cannot take niri down). layout.kdl / misc.kdl lose their colours.
  Two stale comments corrected: 26.04 knows both `optional=true` and `~`.
  Mod+Shift+T cycles.
- herdr: `[theme] name = "terminal"` — every token an ANSI slot, so kitty
  paints herdr, and re-paints it live: herdr enables DEC 2031, kitty reports
  the background change as CSI ?997;n on reload, herdr re-queries OSC 10/11/4
  (source-verified in the pinned 0.9.0, then seen live: the projector followed
  noir -> claude-dark -> noir with no herdr write-back). There was no [theme]
  section before, i.e. herdr was on default catppuccin, not noir.
- nvim: RAW lua/theme.lua dofile()s the fragment (noir fallback inline) and
  owns the catppuccin setup, bufferline's ground highlights and lualine's two
  colours; reload() purges catppuccin, re-setups, :CatppuccinCompile, and is
  called over --remote-expr on every running instance. plugins.lua.in's
  catppuccin block moves there. Deleted: lua/plugins.lua (a stale tracked
  duplicate of the rendered file) and lua/plugins/bufferline.lua (linked,
  never required — plugins.lua.in set bufferline up inline).
- fish: conf.d/colors.fish sources the fragment and re-sources it at the next
  prompt when the pointer moved. No universal variables — fish_variables is
  tracked here and `set -U` would dirty the checkout on every switch.
- starship: the one `#F47B85` becomes `red`; everything else was ANSI-named
  and `#DA7756` (accent-brand) is identical in Claude's light and dark tokens.
- Claude Code: `theme` writes the light/dark key into ~/.claude.json
  (tmp+rename; next start).
- Not in this PR, by Tom's ruling: GTK/MacTahoe stays Dark (Phase 4 in the
  doc; the package already builds the Light variants). cliamp 1.63.2 lists only
  built-in themes, so its catppuccin-noir.toml was inert already; zathura and
  qt6ct untouched.

Verified: `nix build` of the coordinator Home Manager generation and eval of the
client's; `niri validate`; the noir fragment reproduces the previous kitty.conf
colours key for key (only background_opacity is outside it); live on the
coordinator through a preview render (`THEMES_DIR=~/.cache/theme-preview theme
claude-dark`): 5 kitty instances reloaded, niri reloaded, herdr repainted,
back to noir. After the switch the activation entry re-targets that preview
pointer to ~/.config/themes/noir.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rs, F2 pickers

Tom 2026-09-17: "the hardest thing to make sure that you got right is actually
the gtk theme … for my 'noir' theme i am doing nix replacements on an existing
package. the same can be done for the other gtk themes to follow the
'classic'/vanilla claude light and claude dark themes. i want those as well";
"should be able to switch the 'accent' as well … matching folder colors …
matching exactly the wallpaper color selection"; "F2 should be ONLY for the
dark/light/noir variants … Shift + F2 the switch for the color accent
(wallpaper + icons)".

GTK. pkgs/mactahoe-gtk-theme.nix takes a `variant`: `oled` is the existing
noir build, unchanged output; `claude` recolours BOTH branches of
src/sass/_colors.scss to claude.ai's tokens (bg-000/100/200, text-000/200/400,
cds-text-secondary, links = accent-100) with the `orange` accent slot set to
Anthropic clay #D97757 — MacTahoe-Claude-{Dark,Light}[-solid]-orange[-(x)hdpi].
Each palette carries its GTK theme; home/theme.nix puts every theme's package on
the profile, ships each theme's gtk-4.0/ under ~/.config/themes/<name>/, and
points ~/.config/gtk-4.0/{gtk.css,gtk-dark.css,assets} THROUGH ~/.config/theme
at it. `theme apply` sets gtk-theme, wm theme, icon-theme and color-scheme
with gsettings.

Why that works now and did not before. GTK_THEME (modules/common.nix) is
deleted: an env var cannot change under a running session. It had been the
only thing theming GTK because gsettings-desktop-schemas was never on
XDG_DATA_DIRS — no gschemas.compiled with org.gnome.desktop.interface anywhere
in the session — so GTK3's Wayland backend fell back to settings.ini. With the
schema re-homed to share/glib-2.0/schemas (systemPackages) GTK3 follows the
gsettings value LIVE; verified with gtk-query-settings: unset → Dark (settings.ini),
schema present → the dconf value, changed → follows. `gsettings` itself was on
no session PATH, so startup.kdl's four gsettings lines had been failing
silently; glib is in systemPackages and the four lines become
`spawn-at-startup "theme" "apply"`. dconf.settings no longer pins gtk-theme /
color-scheme (a pin would snap a light session back to Dark on every switch).

Accents. The seven claude.ai/imagine grounds (oat olive cactus sky fig heather
coral), rendered at 5120x2880 from the recovered SVG like the olive one.
bin/wallpaper takes an accent name, remembers it in ~/.local/state/wallpaper/
accent (restored at login), and re-picks the folder colour.
pkgs/mactahoe-icon-theme.nix prebuilds MacTahoe-<accent>{,-light,-dark} for all
seven — the folder SVGs of the grey set with #686868 swapped for the accent's
darker ground (--bg-primary-dark) — so every combination is in the store
before it is picked; 163 MB for 24 dirs after jdupes (was 112 MB for 3).
`theme icons` = MacTahoe[-<accent>]-{dark,light}: accent from the wallpaper,
polarity from the theme, stock blue folders when the variant is missing.

Pickers. F2 → bin/theme-prompt theme (noir / claude-dark / claude-light),
Shift+F2 → theme-prompt accent, both the F1/F9/F10 floating-fzf shape with a
theme-prompt window rule. `theme` also gained a flock and a SIGUSR1 fallback
for kitties without our listen_on (from the Omarchy / DankMaterialShell
research, ~/colors/quickshell-live-theming.md).

Live: kitty, niri, herdr, fish, GTK3, folder icons, Chrome's colour scheme.
Next start: GTK4/libadwaita CSS (colour scheme flips live), agent TUIs.
Not here: fonts (another session owns that spec; proprietary, NAS requireFile
pattern only), cliamp/zathura/qt6ct, Claude Code's `custom:` theme file (the
mechanism is in the 2.1.266 binary, the file format is unverified).

Verified: coordinator home-manager-generation builds; coordinator and client
system toplevels evaluate; both MacTahoe packages build with the expected
hexes in gtk-3.0/gtk-4.0 CSS and the olive hex in MacTahoe-olive folders;
niri validate; `wallpaper olive` / `theme apply` / both picker lists live.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tom 2026-09-17: "i'm not using either of those TUI projects at all. so let's
delete them."

zathura: the config dir (home/dot_config/zathura), its configDirs entry and
the package. Nothing else referenced it; PDFs have no mimeApps binding to it.

cliamp: the package (pkgs/cliamp.nix + overlay entry), home/dot_config/cliamp,
its two xdg.configFile links, the one-time cliampRealDir activation (safe to
drop: a generation with no .config/cliamp path at all lets Home Manager's
cleanup remove an old whole-dir link on its own), the cliamp fish wrapper
function and the m/music/mshuffle/mp/mn/mb/mnow aliases, the .gitignore block
for its runtime files, and bin/media's cliamp preference (it now drives
playerctl's default player).

Kept: Navidrome itself, `mscan`/navidrome-scan (its Subsonic client id is now
`navidrome-scan`), and the navidrome-credentials secret, which navidrome-scan
still reads — only the comments in secrets.nix, modules/secrets.nix and
hosts/coordinator/services.nix that named cliamp as its consumer change. No
rekey. The piri music scratchpad stays; its "later a cliamp-in-kitty pane"
notes go. Historical records (DECISIONS.md, the zenbook return doc, a test
comment) are left as written.

Not touched: ~/.config/cliamp on the coordinator is a real directory holding
cliamp's history.toml and resume.json (which carries a Navidrome Subsonic
token, DECISIONS.md 2026-09-13); the switch removes only the two links.

Verified: coordinator home-manager-generation builds with no cliamp or zathura
in home-files or home-path; coordinator, client and nas toplevels evaluate;
niri validate; fish -n on the edited fish files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ch works

Opening nvim on the coordinator after pulling #417, before its switch:

  lua/plugins/lualine.lua:144: module 'theme' not found

lualine.lua is a per-file RAW link, so it already had the new
`require('theme')`; lua/theme.lua was a NEW nvimRaw entry, and per-file
links only appear on a switch. Exactly the "checkout ahead of its switch"
case the design doc promised would degrade gracefully, and nvim did not.

- theme.lua moves to home/dot_local/bin/nvim-lua/. ~/.local/bin is ONE
  out-of-store link into the checkout, and init.lua appends
  ~/.local/bin/nvim-lua/?.lua to package.path before plugins load (the same
  path claude_slash.lua already rides), so require('theme') resolves as soon
  as the checkout has the file — no rebuild in either direction.
- nvim.nix drops the lua/theme.lua nvimRaw entry.
- lualine.lua pcalls the require, with noir's two colours as fallback, so a
  missing module can never cost the status line again.
- `theme`'s live reload (`require("theme").reload()` over --remote-expr) is
  unchanged: same module name, same package.path.

Verified on the coordinator's CURRENT (pre-switch) generation: nvim --headless
fires VeryLazy, require('theme') returns the noir palette, lualine loads and
sets its highlights. home-manager-generation builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fig dir; drop the dead claude-credentials seed

Two Claude Code seats live on the coordinator: cc in ~/.claude (the personal
Claude Max login) and cc2 in ~/.claude-work (the leger.run login). Tom re-logged
both by hand on 2026-09-22; this makes the arrangement durable.

- fish: cc/cac/cc2/cac2 become one launcher. Claude Code never saves workspace
  trust for $HOME (docs: "trust acceptance is held for the current session only
  and is not written to disk"), so a seat typed in ~ showed "Accessing
  workspace" on every launch; the launcher now moves into $CLAUDE_ENVELOPE
  (default ~/today) when started from ~. `cc` clears CLAUDE_CONFIG_DIR
  explicitly: a terminal opened from a cc2 session inherits it, and the old
  alias then silently started cc2 (measured with a stub claude under
  runtime-test). TALLY_SEAT names the seat to hooks and receipts. cc3 lines are
  untouched.
- secrets: remove the claude-credentials seed and its age file. The token had
  been dead since the 2026-08-04 rotation, and a seed that fires whenever
  ~/.claude/.credentials.json is absent could only revert a fresh /login after
  a rebuild. Verified: nix eval of the coordinator's age.secrets no longer lists
  it and the other nineteen are intact.
- AGENTS.md: the seats paragraph (which login is which directory, the launch
  rule, where the meters are).

Not a rebuild, not a merge; both are Tom's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
home/dot_config/ghostty/config.ghostty mirrors kitty.conf key for key so a cmux terminal pane looks like a kitty window, and names the gap wherever Ghostty has no equivalent key. home/home.nix adds ghostty to configDirs so the out-of-store symlink is declared: ~/.config/ghostty already points into this checkout (created 2026-09-17) but is absent from generation 239's home-manager-files, so it is an orphan today and this is what makes it legitimate. home/themes/default.nix renders a per-theme ghostty fragment alongside kitty.conf. docs/theme-switcher-2026-09-17.md gains the consumer row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comments only, verified: zero non-comment added or removed lines across the three files, so no partition table, device id, size, mountpoint or filesystem change. Records which disks belong to whom and which depart with the worker chassis. Per FRONT-10 no agent invents or enforces the return date.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nvim: load the theme module from nvim-lua/ (fixes "module 'theme' not found" before the switch)
modules/halogen.nix opened `port` on `lanInterface` for every host that set
`services.halogen.enable`. modules/strix.nix declares the server on BOTH twins
and splits only `autoStart`, so the coordinator — which by design serves no
model — was admitting :8731 on wlp192s0, its wifi uplink. The live box had the
rule (`-A nixos-fw -i wlp192s0 -p tcp --dport 8731 -j nixos-fw-accept`) with
nothing listening behind it: an open door to an empty room, which `halogen-switch
flash` would have furnished with an unauthenticated inference endpoint.

The "no authentication is fine, it is admitted on the LAN interface only; every
client on that segment is a pinned house device" argument at the top of this
module is true of the worker's wired enp191s0. It was never a claim about the
coordinator's wifi segment.

New `openLanPort`, defaulting to `autoStart`, now gates the firewall line.
Declaring the server and serving the fleet become separate statements: the
worker keeps its door, the coordinator loses one it was never meant to have,
and a host that genuinely wants to serve sets one visible option instead of
inheriting it from `enable`. Coordinator-local `halogen-switch` use is
unaffected — loopback is never filtered.

flake.nix:1695 already asserted exactly this and had been failing since
2026-09-16, taking `checks.nas-topology` — and everything evaluated after it —
down with it. `nix flake check --offline --no-build` is green again. No
change to the assert; it was right all along.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… verb

The hook blocked every session close and Claude Code reported the abort as
`SessionEnd hook [...] failed: Hook cancelled` each time. The script was not at
fault — it always exited 0, always logged its one line, and timed itself out
under the hook timeout. The shape is what fails: the session process WAITS for
a SessionEnd hook before it exits, and the runs that actually harvested took up
to 57 s.

Its own ledger is the argument against it. Of the last 101 runs: 46 created a
note, 1 updated one, 54 skipped — mostly `one cleaned user/assistant turn
exceeds the declared utility context`. Every close paid the latency; fewer than
half bought anything.

Removed: the script, its home.nix link, the SessionEnd block in
home/dot_claude/settings.json, checks.ai-memory-harvest-hook,
tests/ai-memory-hook/, tools/mem-2-hook-oracle.sh, tools/mem-2-eval-probe.sh.

Kept: ai_memory.py, the `harvest` verb, its --enqueue leg (FIX-E08, #348) and
MEM-3's probe. The `drain` skill still runs the verb on demand, so only the
automatic leg is gone, not the capability.

The removed check asserted there was no SessionStart block, so a hook naming an
unshipped script could not come back by accident (the dead herdr-agent-state.sh
one, removed 2026-09-13). That guard is not lost — checks.no-claude-code-hooks
replaces it and is strictly broader: it fails on ANY hook block in settings.json
and on anything delivered into ~/.claude/hooks, which stays a real, writable
directory owned by no link. Re-adding a hook is now a deliberate edit to it.

docs/local-ai/harvest-on-close.md is kept and banner-marked as removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`nix flake update llm-agents`, the documented way to move the agent catalog
without touching the kernel/Mesa pin. From 4a6df59b (2026-09-09) to f54d9ae0
(2026-09-23), carrying its own nixpkgs (2026-09-08 -> 2026-09-22) and bun2nix.

  claude-code       2.1.266 -> 2.1.280
  codex             0.153.4 -> 0.155.1
  ccusage                      20.0.24
  claude-agent-acp             0.81.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mecattaf and others added 2 commits September 23, 2026 23:38
…(DF-6)

a775d58 (remove the SessionEnd harvest hook) also carried the
home-profiles asserts from f1d689b (#448, parakeet socket activation),
which this branch does not contain. The coordinator home here still has
parakeet-service with Install.WantedBy = [ "default.target" ] and no
socket, so checks.home-profiles failed with
`attribute 'parakeet-service' missing` at flake.nix:2036.

Restore main's asserts for this check. The socket asserts belong in #448
and return when it merges. No module or host change; only the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f7b3423981d66d17e32ff34fc3226ac4dc3eb72e)
@mecattaf
mecattaf changed the base branch from theme-switcher to main September 23, 2026 21:39
@mecattaf

Copy link
Copy Markdown
Owner Author

Retargeted to main (overnight run 09-23, plan A1, after #466 landed Tom's E13 '8731-close'). Merged main 7d4704db in (53f91f5e, clean: 334e5964 and 204c96a0 are the same change) and cherry-picked the DF-6 fix f7b34239 (eed39281), because #448 is not on main. nix flake check --no-build rc 0 on eed39281 (MEASURED). Note: this branch carries #417 (theme-switcher, incl. #421) since it was stacked on it; merging this lands #417's commits on main too.

@mecattaf
mecattaf merged commit 5eca46c into main Sep 23, 2026
mecattaf added a commit that referenced this pull request Sep 23, 2026
Conflicts in flake.nix (checks), hosts/coordinator and hosts/worker: both
sides are additive (ax-fleet checks and myAxFleet vs DF-5/G1 checks and
myGvisor.enable = false); kept both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

halogen: the coordinator opens the unauthenticated :8731 inference port on its wifi uplink (flake check nas-topology is red)

1 participant