diff --git a/cancelok/cron.fish b/cancelok/cron.fish
index 60be5e695..288e2b78e 100644
--- a/cancelok/cron.fish
+++ b/cancelok/cron.fish
@@ -22,11 +22,37 @@ set -l PLIST "$HOME/Library/LaunchAgents/$LABEL.plist"
set -l LOG "$HOME/Library/Logs/cancelok.log"
set -l CMD $argv[1]
-# The loop needs serve-local (the gate measures against it) and `claude` (the
-# generator IS claude). launchd starts with a bare PATH, so we hand it the real
-# one rather than discovering at 3am that node isn't on it.
-set -l NODE (which node)
-set -l REALPATH (dirname $NODE):/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
+# launchd starts with a bare PATH — it does not source your shell — so every
+# binary has to be handed over as an absolute path it will still be able to find
+# after a reboot.
+#
+# `which node` is a TRAP here: under fnm it resolves to a per-shell temp dir with
+# a PID in the name (…/fnm_multishells/36675_…/bin/node). Baking that into a
+# launchd plist works today and dies the next time you log in, and the failure is
+# invisible because the cron just quietly stops making anything. So we resolve the
+# STABLE fnm alias instead, and fall back to homebrew.
+set -l NODE
+for candidate in "$HOME/.local/share/fnm/aliases/default/bin/node" /opt/homebrew/bin/node /usr/local/bin/node
+ if test -x $candidate
+ set NODE $candidate
+ break
+ end
+end
+if test -z "$NODE"
+ echo "❌ no stable node found — refusing to install a cron that will rot."
+ exit 1
+end
+
+# `claude` IS the generator, and it lives in ~/.local/bin, which launchd has never
+# heard of.
+set -l CLAUDE (command -v claude)
+if test -z "$CLAUDE"; set CLAUDE "$HOME/.local/bin/claude"; end
+if not test -x $CLAUDE
+ echo "❌ claude not found at $CLAUDE — the generator cannot run."
+ exit 1
+end
+
+set -l REALPATH (dirname $NODE):(dirname $CLAUDE):/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
switch "$CMD"
case install
@@ -40,10 +66,13 @@ switch "$CMD"
/bin/sh
-lc
- cd $REPO; curl -sf -o /dev/null http://localhost:8899/ || (node marketing/av-reels/bin/serve-local.mjs & sleep 3); node cancelok/loop.mjs --tries 2 --no-ship
+ cd $REPO; curl -sf -o /dev/null http://localhost:8899/ || ($NODE marketing/av-reels/bin/serve-local.mjs & sleep 3); exec $NODE cancelok/loop.mjs --tries 2 --no-ship
EnvironmentVariables
- PATH$REALPATH
+
+ PATH$REALPATH
+ CANCELOK_CLAUDE$CLAUDE
+
StartInterval3600
RunAtLoad
StandardOutPath$LOG
diff --git a/cancelok/evolve.mjs b/cancelok/evolve.mjs
index bc509de11..e50972af5 100644
--- a/cancelok/evolve.mjs
+++ b/cancelok/evolve.mjs
@@ -27,6 +27,10 @@ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
import { resolve } from "node:path";
import { REPO, crowd, pheromone, lineage, remember } from "./taste.mjs";
+// launchd has never heard of ~/.local/bin, so the cron hands us an absolute
+// path. A bare "claude" works in a terminal and ENOENTs at 3am.
+const CLAUDE = process.env.CANCELOK_CLAUDE || "claude";
+
const DISKS = resolve(REPO, "system/public/aesthetic.computer/disks");
const ENGINE = resolve(REPO, "system/public/aesthetic.computer/lib/pads.mjs");
const dry = process.argv.includes("--dry");
@@ -178,7 +182,7 @@ if (dry) {
console.log(`🧬 breeding ${mum.code} — mutation: ${mutation.key}${mutation.dad ? ` × ${mutation.dad}` : ""}`);
// A language model, not an agent: `claude --print` can WRITE FILES, and a
// generator that writes its own pad collides with itself on the name check.
-const raw = execFileSync("claude", ["--print", "--model", "sonnet", "--tools", ""], {
+const raw = execFileSync(CLAUDE, ["--print", "--model", "sonnet", "--tools", ""], {
input: prompt,
encoding: "utf8",
maxBuffer: 1024 * 1024 * 8,
diff --git a/cancelok/generate.mjs b/cancelok/generate.mjs
index 32b2b0612..a8528444b 100644
--- a/cancelok/generate.mjs
+++ b/cancelok/generate.mjs
@@ -14,6 +14,10 @@ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
import { resolve } from "node:path";
import { REPO, pheromone } from "./taste.mjs";
+// launchd has never heard of ~/.local/bin, so the cron hands us an absolute
+// path. A bare "claude" works in a terminal and ENOENTs at 3am.
+const CLAUDE = process.env.CANCELOK_CLAUDE || "claude";
+
const DISKS = resolve(REPO, "system/public/aesthetic.computer/disks");
const ENGINE = resolve(REPO, "system/public/aesthetic.computer/lib/pads.mjs");
const EXAMPLE = resolve(DISKS, "wispo.mjs");
@@ -81,7 +85,7 @@ console.log("🧠 generating…");
// writes its own pad will then collide with itself when we check the name. We
// want a language model here, not an agent: it returns text, WE decide what
// lands on disk. (This cost a whole generation to learn.)
-const raw = execFileSync("claude", ["--print", "--model", "sonnet", "--tools", ""], {
+const raw = execFileSync(CLAUDE, ["--print", "--model", "sonnet", "--tools", ""], {
input: prompt,
encoding: "utf8",
maxBuffer: 1024 * 1024 * 8,
diff --git a/cancelok/lineage.json b/cancelok/lineage.json
index 520d2dafe..99dabc752 100644
--- a/cancelok/lineage.json
+++ b/cancelok/lineage.json
@@ -5,5 +5,47 @@
"mutation": "cross",
"trait": "pixel-sort / datamosh field (from vex) x pendulum-wave bell/pluck voice, scored by just-intonation dissonance (from drolo)",
"born": "2026-07-14T06:03:23.577Z"
+ },
+ "wandro": {
+ "parent": "wexo",
+ "second": "drolo",
+ "mutation": "cross",
+ "trait": "pendulum-row visual (wexo) x just-intonation layered voice (drolo)",
+ "born": "2026-07-14T06:33:06.854Z"
+ },
+ "welto": {
+ "parent": "wexo",
+ "second": null,
+ "mutation": "push",
+ "trait": "a pendulum wave you can grab and pull taut x pentatonic scrub + slingshot pluck",
+ "born": "2026-07-14T08:11:25.899Z"
+ },
+ "vosk": {
+ "parent": "vex",
+ "second": null,
+ "mutation": "voice",
+ "trait": "pixel-sort/datamosh glitch field x slow glass-flute drone",
+ "born": "2026-07-14T10:27:57.728Z"
+ },
+ "dravo": {
+ "parent": "drolo",
+ "second": "wexo",
+ "mutation": "cross",
+ "trait": "pendulum-wave row (drolo) x bottom-center pluck detection (wexo)",
+ "born": "2026-07-14T12:36:20.665Z"
+ },
+ "glavo": {
+ "parent": "vex",
+ "second": "tavo",
+ "mutation": "cross",
+ "trait": "pixel-sort/datamosh visual generator (vex) x bubble-bath synth voice (tavo)",
+ "born": "2026-07-14T13:39:43.347Z"
+ },
+ "vexen": {
+ "parent": "wexo",
+ "second": null,
+ "mutation": "invert",
+ "trait": "struck pendulum row that decays into stillness x pentatonic pluck fading to hush",
+ "born": "2026-07-14T14:43:43.424Z"
}
}