From cff6eacce2766f85e1e1efd710ec453c0570a6be Mon Sep 17 00:00:00 2001 From: debedb Date: Tue, 22 Sep 2026 10:49:40 -0700 Subject: [PATCH] Promote cmux-node-options-tmpdir-guard; launchd cross-ref (bundle 1.130.0) The launchd-env-sync-session-leak branch carried a "Related" pointer to cmux-node-options-tmpdir-guard that never landed, because the target was only ever a personal skill under ~/.claude/skills - a shadow copy nobody else has. Adding the pointer alone would have shipped a dangling reference in a public repo, so the target comes with it. cmux launches Claude Code with a --require shim whose .cjs lives under $TMPDIR. macOS reaps files there once they go untouched for about three days, which is exactly the window a reboot-surviving cmux session lives past. The file disappears while NODE_OPTIONS still names it, and from then on every node/claude invocation in that session dies on a missing --require target - a failure that reads as a broken toolchain and arrives days after anything changed. The skill carries the stopgap for builds predating manaflow-ai/cmux#3699: a persistent copy under $HOME, and a LaunchAgent that recreates the $TMPDIR file and refreshes its mtime daily, well inside the reap window. The subtle part is why the LaunchAgent can find the right file at all - it runs in the same per-user launchd context as the GUI app, so getconf DARWIN_USER_TEMP_DIR returns the same per-boot temp base cmux used, and nothing has to be hardcoded. The personal copy's closing reference pointed at a memory note with no public counterpart; it is replaced by a pointer back to launchd-env-sync-session-leak, which is the same class of failure from the other direction - a stale value synced into launchd, rather than a guard file reaped out from under one. Versions: bundle 1.130.0; launchd-env-sync-session-leak skill 1.1.0 -> 1.2.0, and its standalone plugin 1.0.0 -> 1.2.0, which had been left behind at 1.0.0 by an earlier skill bump. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 5 + .codex-plugin/marketplace.json | 5 + README.md | 2 + catalog.json | 23 ++++ .../.claude-plugin/plugin.json | 12 ++ .../.codex-plugin/plugin.json | 19 +++ .../skills/cmux-node-options-tmpdir-guard | 1 + .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- plugins/skillz/.claude-plugin/plugin.json | 2 +- plugins/skillz/.codex-plugin/plugin.json | 2 +- .../cmux-node-options-tmpdir-guard | 1 + .../cmux-node-options-tmpdir-guard | 1 + .../cmux-node-options-tmpdir-guard/SKILL.md | 121 ++++++++++++++++++ skills/launchd-env-sync-session-leak/SKILL.md | 4 +- 15 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 plugins/cmux-node-options-tmpdir-guard/.claude-plugin/plugin.json create mode 100644 plugins/cmux-node-options-tmpdir-guard/.codex-plugin/plugin.json create mode 120000 plugins/cmux-node-options-tmpdir-guard/skills/cmux-node-options-tmpdir-guard create mode 120000 plugins/skillz/skills-claude/cmux-node-options-tmpdir-guard create mode 120000 plugins/skillz/skills-codex/cmux-node-options-tmpdir-guard create mode 100644 skills/cmux-node-options-tmpdir-guard/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 798e67f8..c9866469 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -391,6 +391,11 @@ "source": "./plugins/claudeception/", "description": "Continuous-learning meta-skill for both hosts:" }, + { + "name": "cmux-node-options-tmpdir-guard", + "source": "./plugins/cmux-node-options-tmpdir-guard/", + "description": "Keep cmux's Claude NODE_OPTIONS restore guard alive when it lives under $TMPDIR. Use when: (1) a long-lived cmux/Claude session suddenly has every node/claude invocation fail with a missing --require target, (2) NODE_OPTIONS points at a /var/folders/.../T/cmux-claude-node-options/restore-node-options.cjs path on a cmux build that predates manaflow-ai/cmux#3699, (3) reboot-surviving cmux sessions break after about three days idle. Root cause: macOS reaps $TMPDIR files untouched for about three days, so the required guard file vanishes while NODE_OPTIONS still references it. Covers the persistent-HOME-copy stopgap, the LaunchAgent that recreates and mtime-refreshes the $TMPDIR file daily, why a LaunchAgent resolves the same per-user temp base, and when to retire it." + }, { "name": "cmux-autoresume-after-reboot", "source": "./plugins/cmux-autoresume-after-reboot/", diff --git a/.codex-plugin/marketplace.json b/.codex-plugin/marketplace.json index 0b04fd21..325eaa62 100644 --- a/.codex-plugin/marketplace.json +++ b/.codex-plugin/marketplace.json @@ -396,6 +396,11 @@ "source": "./plugins/claudeception/", "description": "Continuous-learning meta-skill for both hosts:" }, + { + "name": "cmux-node-options-tmpdir-guard", + "source": "./plugins/cmux-node-options-tmpdir-guard/", + "description": "Keep cmux's Claude NODE_OPTIONS restore guard alive when it lives under $TMPDIR. Use when: (1) a long-lived cmux/Claude session suddenly has every node/claude invocation fail with a missing --require target, (2) NODE_OPTIONS points at a /var/folders/.../T/cmux-claude-node-options/restore-node-options.cjs path on a cmux build that predates manaflow-ai/cmux#3699, (3) reboot-surviving cmux sessions break after about three days idle. Root cause: macOS reaps $TMPDIR files untouched for about three days, so the required guard file vanishes while NODE_OPTIONS still references it. Covers the persistent-HOME-copy stopgap, the LaunchAgent that recreates and mtime-refreshes the $TMPDIR file daily, why a LaunchAgent resolves the same per-user temp base, and when to retire it." + }, { "name": "cmux-autoresume-after-reboot", "source": "./plugins/cmux-autoresume-after-reboot/", diff --git a/README.md b/README.md index 40b16518..46a848d7 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ environments and older Codex versions. | [agent-traffic-log](./skills/agent-traffic-log/SKILL.md) | skill | Claude, Codex | Append-only JSONL log of agent-to-agent traffic + a live pane; lock-free concurrent appends, and `xs status` derives who is blocked from the events | | [terraform-noninteractive-prod-apply](./skills/terraform-noninteractive-prod-apply/SKILL.md) | skill | Claude, Codex | Non-interactive terraform **prod** apply when `apply.sh` has no `-auto-approve`: use `plan -out` + `apply `, not `echo yes \|`, because a prompt-piped apply approves a plan terraform **recomputes** rather than the one you reviewed | | [cmux-config-silent-drop-triage](./skills/cmux-config-silent-drop-triage/SKILL.md) | skill | Claude, Codex | A cmux.json entry passes `config doctor` but never appears: doctor is syntax-only, so bisect the backups and read the `[CmuxConfig]` diagnostics and enum values off the binary's `strings` | +| [cmux-node-options-tmpdir-guard](./skills/cmux-node-options-tmpdir-guard/SKILL.md) | skill | Claude, Codex | Keep cmux's Claude `NODE_OPTIONS` restore guard alive when it lives under `$TMPDIR`: macOS reaps files there after ~3 days untouched, so a long-lived session's `--require` target vanishes and every `node`/`claude` invocation in it dies. Persistent `$HOME` copy plus a daily LaunchAgent that recreates and mtime-refreshes the file | | [alb-per-rule-traffic-attribution](./skills/alb-per-rule-traffic-attribution/SKILL.md) | skill | Claude, Codex | ALB publishes no per-listener-rule CloudWatch metric; access logs answer it, but `matched_rule_priority` is a position that renumbers when a rule is inserted - attribute by `request_url` + `user_agent` | | [cmux-claude-codex-cross-runtime-messaging](./skills/cmux-claude-codex-cross-runtime-messaging/SKILL.md) | skill | Claude, Codex | Claude Code <-> Codex CLI agents as messaging peers in cmux: `cmux send` transport, self-named tabs, both sides in the traffic log, where the three transcripts live | | [`alb-per-rule-traffic-attribution` plugin](./plugins/alb-per-rule-traffic-attribution/) | plugin | Claude, Codex | Single-skill plugin: alb-per-rule-traffic-attribution | @@ -191,6 +192,7 @@ environments and older Codex versions. | [`cloudwatch-per-host-stat-single-host-vs-fleet` plugin](./plugins/cloudwatch-per-host-stat-single-host-vs-fleet/) | plugin | Claude, Codex | Single-skill plugin: cloudwatch-per-host-stat-single-host-vs-fleet | | [`cmux-autoresume-after-reboot` plugin](./plugins/cmux-autoresume-after-reboot/) | plugin | Claude, Codex | Single-skill plugin: cmux-autoresume-after-reboot | | [`cmux-config-silent-drop-triage` plugin](./plugins/cmux-config-silent-drop-triage/) | plugin | Claude, Codex | Single-skill plugin: cmux-config-silent-drop-triage | +| [`cmux-node-options-tmpdir-guard` plugin](./plugins/cmux-node-options-tmpdir-guard/) | plugin | Claude, Codex | Single-skill plugin: cmux-node-options-tmpdir-guard | | [`cmux-cross-session-visibility` plugin](./plugins/cmux-cross-session-visibility/) | plugin | Claude, Codex | Single-skill plugin: cmux-cross-session-visibility | | [`cmux-session-self-identity` plugin](./plugins/cmux-session-self-identity/) | plugin | Claude, Codex | Single-skill plugin: cmux-session-self-identity | | [`git-worktree-add-relative-path-nests-inside-repo` plugin](./plugins/git-worktree-add-relative-path-nests-inside-repo/) | plugin | Claude, Codex | Single-skill plugin: git-worktree-add-relative-path-nests-inside-repo | diff --git a/catalog.json b/catalog.json index 28894664..55759bfe 100644 --- a/catalog.json +++ b/catalog.json @@ -273,6 +273,15 @@ ], "summary": "Make AI agents show as watchable cmux tabs; Claude needs the claude-teams wrapper, Codex tabs via codex-teams/hooks." }, + { + "name": "cmux-node-options-tmpdir-guard", + "path": "skills/cmux-node-options-tmpdir-guard/SKILL.md", + "hosts": [ + "claude", + "codex" + ], + "summary": "Keep cmux's Claude NODE_OPTIONS restore guard alive when it lives under $TMPDIR: macOS reaps $TMPDIR files untouched for about three days, so a long-lived session's --require target vanishes and every node/claude invocation in it dies. Covers the persistent-HOME-copy stopgap, the daily LaunchAgent that recreates and mtime-refreshes the file, why a LaunchAgent resolves the same per-user temp base, and when to retire it." + }, { "name": "cmux-autoresume-after-reboot", "path": "skills/cmux-autoresume-after-reboot/SKILL.md", @@ -1127,6 +1136,7 @@ "cloudwatch-metric-filter-dimensions-default-value-exclusive", "cloudwatch-per-host-stat-single-host-vs-fleet", "cmux-agent-tabs", + "cmux-node-options-tmpdir-guard", "cmux-autoresume-after-reboot", "cmux-claude-codex-cross-runtime-messaging", "cmux-config-silent-drop-triage", @@ -2213,6 +2223,19 @@ ], "summary": "Single-skill plugin: claudeception." }, + { + "name": "cmux-node-options-tmpdir-guard", + "claude_manifest": "plugins/cmux-node-options-tmpdir-guard/.claude-plugin/plugin.json", + "codex_manifest": "plugins/cmux-node-options-tmpdir-guard/.codex-plugin/plugin.json", + "hosts": [ + "claude", + "codex" + ], + "skills": [ + "cmux-node-options-tmpdir-guard" + ], + "summary": "Single-skill plugin: cmux-node-options-tmpdir-guard." + }, { "name": "cmux-autoresume-after-reboot", "claude_manifest": "plugins/cmux-autoresume-after-reboot/.claude-plugin/plugin.json", diff --git a/plugins/cmux-node-options-tmpdir-guard/.claude-plugin/plugin.json b/plugins/cmux-node-options-tmpdir-guard/.claude-plugin/plugin.json new file mode 100644 index 00000000..476e5df2 --- /dev/null +++ b/plugins/cmux-node-options-tmpdir-guard/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "cmux-node-options-tmpdir-guard", + "version": "1.0.0", + "description": "Keep cmux's Claude NODE_OPTIONS restore guard alive when it lives under $TMPDIR. Use when: (1) a long-lived cmux/Claude session suddenly has every node/claude invocation fail with a missing --require target, (2) NODE_OPTIONS points at a /var/folders/.../T/cmux-claude-node-options/restore-node-options.cjs path on a cmux build that predates manaflow-ai/cmux#3699, (3) reboot-surviving cmux sessions break after about three days idle. Root cause: macOS reaps $TMPDIR files untouched for about three days, so the required guard file vanishes while NODE_OPTIONS still references it. Covers the persistent-HOME-copy stopgap, the LaunchAgent that recreates and mtime-refreshes the $TMPDIR file daily, why a LaunchAgent resolves the same per-user temp base, and when to retire it.", + "author": { + "name": "voitta-ai" + }, + "homepage": "https://github.com/voitta-ai/skillz", + "repository": "https://github.com/voitta-ai/skillz", + "license": "MIT", + "skills": "./skills/" +} diff --git a/plugins/cmux-node-options-tmpdir-guard/.codex-plugin/plugin.json b/plugins/cmux-node-options-tmpdir-guard/.codex-plugin/plugin.json new file mode 100644 index 00000000..4283bbaf --- /dev/null +++ b/plugins/cmux-node-options-tmpdir-guard/.codex-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "cmux-node-options-tmpdir-guard", + "version": "1.0.0", + "description": "Keep cmux's Claude NODE_OPTIONS restore guard alive when it lives under $TMPDIR. Use when: (1) a long-lived cmux/Claude session suddenly has every node/claude invocation fail with a missing --require target, (2) NODE_OPTIONS points at a /var/folders/.../T/cmux-claude-node-options/restore-node-options.cjs path on a cmux build that predates manaflow-ai/cmux#3699, (3) reboot-surviving cmux sessions break after about three days idle. Root cause: macOS reaps $TMPDIR files untouched for about three days, so the required guard file vanishes while NODE_OPTIONS still references it. Covers the persistent-HOME-copy stopgap, the LaunchAgent that recreates and mtime-refreshes the $TMPDIR file daily, why a LaunchAgent resolves the same per-user temp base, and when to retire it.", + "author": "voitta-ai", + "homepage": "https://github.com/voitta-ai/skillz", + "repository": "https://github.com/voitta-ai/skillz", + "license": "MIT", + "keywords": [ + "claude", + "codex", + "skills", + "cmux", + "macos", + "launchd", + "nodejs" + ], + "skills": "./skills/" +} diff --git a/plugins/cmux-node-options-tmpdir-guard/skills/cmux-node-options-tmpdir-guard b/plugins/cmux-node-options-tmpdir-guard/skills/cmux-node-options-tmpdir-guard new file mode 120000 index 00000000..7283aec6 --- /dev/null +++ b/plugins/cmux-node-options-tmpdir-guard/skills/cmux-node-options-tmpdir-guard @@ -0,0 +1 @@ +../../../skills/cmux-node-options-tmpdir-guard \ No newline at end of file diff --git a/plugins/launchd-env-sync-session-leak/.claude-plugin/plugin.json b/plugins/launchd-env-sync-session-leak/.claude-plugin/plugin.json index f35acdd9..fd646268 100644 --- a/plugins/launchd-env-sync-session-leak/.claude-plugin/plugin.json +++ b/plugins/launchd-env-sync-session-leak/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "launchd-env-sync-session-leak", "description": "Diagnose and undo a login-environment -> launchd sync that was run from inside a multiplexer pane or an AI-agent session, so PROMPT_COMMAND, CMUX_*, CLAUDECODE/CLAUDE_CODE_*,...", - "version": "1.0.0", + "version": "1.2.0", "author": { "name": "voitta-ai" }, diff --git a/plugins/launchd-env-sync-session-leak/.codex-plugin/plugin.json b/plugins/launchd-env-sync-session-leak/.codex-plugin/plugin.json index c3c899b5..77c8ad67 100644 --- a/plugins/launchd-env-sync-session-leak/.codex-plugin/plugin.json +++ b/plugins/launchd-env-sync-session-leak/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "launchd-env-sync-session-leak", - "version": "1.0.0", + "version": "1.2.0", "description": "Diagnose and undo a login-environment -> launchd sync that was run from inside a multiplexer pane or an AI-agent session, so PROMPT_COMMAND, CMUX_*, CLAUDECODE/CLAUDE_CODE_*,...", "author": "voitta-ai", "homepage": "https://github.com/voitta-ai/skillz", diff --git a/plugins/skillz/.claude-plugin/plugin.json b/plugins/skillz/.claude-plugin/plugin.json index 3feb3fc4..1f72fe5f 100644 --- a/plugins/skillz/.claude-plugin/plugin.json +++ b/plugins/skillz/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "skillz", "description": "Skillz bundle: every skill in the catalog except those whose own plugin ships hooks (see catalog.json).", - "version": "1.129.0", + "version": "1.130.0", "author": { "name": "voitta-ai" }, diff --git a/plugins/skillz/.codex-plugin/plugin.json b/plugins/skillz/.codex-plugin/plugin.json index 5f9b42e8..674cc7de 100644 --- a/plugins/skillz/.codex-plugin/plugin.json +++ b/plugins/skillz/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "skillz", - "version": "1.129.0", + "version": "1.130.0", "description": "Skillz bundle: every skill in the catalog except those whose own plugin ships hooks (see catalog.json).", "author": "voitta-ai", "homepage": "https://github.com/voitta-ai/skillz", diff --git a/plugins/skillz/skills-claude/cmux-node-options-tmpdir-guard b/plugins/skillz/skills-claude/cmux-node-options-tmpdir-guard new file mode 120000 index 00000000..7283aec6 --- /dev/null +++ b/plugins/skillz/skills-claude/cmux-node-options-tmpdir-guard @@ -0,0 +1 @@ +../../../skills/cmux-node-options-tmpdir-guard \ No newline at end of file diff --git a/plugins/skillz/skills-codex/cmux-node-options-tmpdir-guard b/plugins/skillz/skills-codex/cmux-node-options-tmpdir-guard new file mode 120000 index 00000000..7283aec6 --- /dev/null +++ b/plugins/skillz/skills-codex/cmux-node-options-tmpdir-guard @@ -0,0 +1 @@ +../../../skills/cmux-node-options-tmpdir-guard \ No newline at end of file diff --git a/skills/cmux-node-options-tmpdir-guard/SKILL.md b/skills/cmux-node-options-tmpdir-guard/SKILL.md new file mode 100644 index 00000000..9db7b6ad --- /dev/null +++ b/skills/cmux-node-options-tmpdir-guard/SKILL.md @@ -0,0 +1,121 @@ +--- +name: cmux-node-options-tmpdir-guard +description: | + Keep cmux's Claude NODE_OPTIONS restore guard alive when it lives under $TMPDIR. + Use when: (1) a long-lived cmux/Claude session suddenly has every `node`/`claude` + invocation fail with a missing `--require` target, (2) NODE_OPTIONS points at + /var/folders/.../T/cmux-claude-node-options/restore-node-options.cjs on a cmux + build that predates manaflow-ai/cmux#3699, (3) reboot-surviving cmux sessions + break after ~3 days idle. Root cause: macOS reaps $TMPDIR files untouched ~3 + days; the required guard file vanishes while NODE_OPTIONS still references it. + Stopgap: persistent HOME copy + a LaunchAgent that recreates and mtime-refreshes + the $TMPDIR file daily. Retire once on a build that writes the guard under $HOME. +author: Claude Code +version: 1.0.0 +date: 2026-07-25 +source: https://github.com/voitta-ai/skillz +source_file: skills/cmux-node-options-tmpdir-guard/SKILL.md +--- + +# cmux NODE_OPTIONS $TMPDIR guard survival + +> **Canonical source.** This skill lives in the repo at +> https://github.com/voitta-ai/skillz (file: +> `skills/cmux-node-options-tmpdir-guard/SKILL.md`). + + +## Problem + +cmux launches Claude Code with a `--require` shim to restore the user's original +`NODE_OPTIONS` inside the agent: + +``` +NODE_OPTIONS=--require=$TMPDIR/cmux-claude-node-options/restore-node-options.cjs --max-old-space-size=4096 +``` + +That required `.cjs` lives under `$TMPDIR` (`/var/folders/.../T`). macOS reaps +files there that go untouched for ~3 days. A cmux/Claude session that outlives that +window (exactly what a reboot-surviving setup encourages) loses the file while +`NODE_OPTIONS` still points at it, so **every** subsequent `node`/`claude` +invocation in the session dies on a missing `--require` target. + +Upstream fix: [manaflow-ai/cmux#3699](https://github.com/manaflow-ai/cmux/pull/3699) +(closes #3463) writes the guard under `$HOME/.claude/cmux` instead. Builds predating +it still use `$TMPDIR`. + +## Context / Trigger Conditions + +- `echo $NODE_OPTIONS` shows a `--require=/var/folders/.../T/cmux-claude-node-options/...` path. +- `~/.claude/cmux/` does not exist (fix #3699 not present). +- Node/Claude subprocesses fail referencing a missing `restore-node-options.cjs`. +- Applies to macOS + a cmux build older than the one that lands #3699. + +## Solution + +The guard content is static and session-independent, so one persistent copy under +`$HOME` covers every session. A LaunchAgent recreates the `$TMPDIR` file and +refreshes its mtime daily — well inside the ~3-day reap window. + +1. Persistent copy (source of truth): + +```bash +mkdir -p ~/.claude/cmux/cmux-claude-node-options +cp "$TMPDIR/cmux-claude-node-options/restore-node-options.cjs" \ + ~/.claude/cmux/cmux-claude-node-options/restore-node-options.cjs +``` + +2. Refresh script `~/.claude/cmux/refresh-node-options-guard.sh` (chmod +x): + +```bash +#!/usr/bin/env bash +set -euo pipefail +src="$HOME/.claude/cmux/cmux-claude-node-options/restore-node-options.cjs" +tmpbase="$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || echo "${TMPDIR:-/tmp}")" +dst_dir="${tmpbase%/}/cmux-claude-node-options" +dst="$dst_dir/restore-node-options.cjs" +[ -f "$src" ] || exit 0 +mkdir -p "$dst_dir" +cp -f "$src" "$dst" # cp refreshes mtime, resetting the reaper clock +``` + +3. LaunchAgent `~/Library/LaunchAgents/