Skip to content

Keep Claude NODE_OPTIONS restore module out of temp cleanup - #3526

Closed
austinywang wants to merge 52 commits into
mainfrom
issue-3512-node-options-tmpdir
Closed

austinywang wants to merge 52 commits into
mainfrom
issue-3512-node-options-tmpdir

Conversation

@austinywang

@austinywang austinywang commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Move the Claude NODE_OPTIONS restore preload from macOS temp storage into Library/Application Support/cmux/node-options for the shell wrapper, Swift CLI path, and remote daemon launcher.
  • Recreate restore-node-options.cjs every time NODE_OPTIONS is computed, so a missing/deleted preload repairs itself on the next Claude launch.
  • Keep resume/env sanitizers aware of both the old temp path and the new durable path so persisted commands do not retain cmux-injected flags.

Why

The bug is a lifecycle mismatch: child processes inherit NODE_OPTIONS=--require=<file>, but the old file lived under $TMPDIR, which macOS can clean while the session is still alive. Once the file disappeared, every child Node process failed to load it and flooded the session.

Tests

  • Added a Swift regression test in the first commit that deletes the restore file, calls the wrapper again, and asserts the file is recreated under Application Support rather than temp storage.
  • bash -n Resources/bin/claude
  • python3 -m py_compile tests/test_claude_wrapper_hooks.py tests/test_cli_claude_teams_env.py
  • git diff --check
  • Manual wrapper smoke with the launched dev app: NODE_OPTIONS points at Library/Application Support/cmux/node-options/restore-node-options.cjs, Node runtime sees restored NODE_OPTIONS, and deleting the file is repaired on the next wrapper invocation.
  • Built and launched with ./scripts/reload.sh --tag issue-3512-node-options-tmpdir --launch.

Note: local Swift tests were not run per repo policy; CI should run them.

Closes #3512


Note

Medium Risk
Touches Claude launch env handling across Swift CLI, shell wrapper, and remote daemon; mistakes could break agent startup or propagate incorrect NODE_OPTIONS in user shells. Changes are well-covered by new Swift/Go/Python regression tests but still impact a critical launch path.

Overview
Moves the Claude NODE_OPTIONS restore preload from temp directories to a durable per-user location (~/Library/Application Support/cmux/node-options), with a validated, secure temp fallback and optional override via CMUX_NODE_OPTIONS_RESTORE_DIR.

Centralizes and hardens NODE_OPTIONS merging/sanitization: quote-aware tokenization, correct quoting of --require paths (spaces/backslashes), stripping only cmux-injected restore --require entries (legacy + durable) and the paired --max-old-space-size=4096, and preserving user-specified flags (including user heap caps). CMUX_ORIGINAL_NODE_OPTIONS* is now set only when the restored value is non-empty.

Introduces a new Swift package CMUXNodeOptions used by the CLI and CMUXAgentLaunch, updates resume/env sanitizers accordingly, and adds focused regression tests (Swift unit tests plus expanded Go and Python wrapper/claude-teams tests) for directory selection, stale preload cleanup, and tricky quoting/escaping cases.

Reviewed by Cursor Bugbot for commit 4a3aac3. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Moves the Claude NODE_OPTIONS restore preload out of temp into a durable per‑user path and rebuilds it on each launch. Aligns quote‑aware sanitization and restore normalization across Swift, Bash, and Go so only cmux‑injected flags are removed and user options are preserved (addresses #3512).

  • Bug Fixes
    • Durable per‑user restore dir with secure temp fallback; normalized CMUX_NODE_OPTIONS_RESTORE_DIR to a managed suffix; hardened validation; component‑aware detection of current/legacy paths; remote daemon uses user config dir; resilient to bad/missing TMPDIR/HOME.
    • Shared, quote‑aware sanitizer via CMUXNodeOptions, Bash, and Go: preserves spaces/double quotes/apostrophes/backslashes; re‑quotes on join; strips cmux --require (legacy + durable) and only the immediately paired --max-old-space-size=4096; preserves user heap caps; Bash 3.2 empty‑array guards; sets CMUX_ORIGINAL_NODE_OPTIONS* only when the normalized original is non‑empty; guard aligned across CLI and resume.

Written for commit 4a3aac3. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Restore preload now lives in a stable per-user Application Support location and can be overridden via CMUX_NODE_OPTIONS_RESTORE_DIR.
  • Bug Fixes

    • Safer tokenization, quoting and reconstruction of NODE_OPTIONS preserves existing quoted require paths, normalizes heap-cap flags, and prevents tmpdir-related leaks while reliably stripping stale injected preload entries. Restore injection now works even when TMPDIR is invalid.
  • Tests

    • Added and expanded tests for quoted paths, durable restore location, merging/token behavior, and resume-time NODE_OPTIONS handling.

The Claude wrapper regression now runs the restore-module setup twice with a simulated live cmux socket. It deletes the first restore file before the second launch, so CI can prove the writer recreates the module and that the path is not tied to TMPDIR cleanup.

Constraint: Swift tests are authored but not run locally per repository policy.

Rejected: Python-only coverage | the issue asks for a Swift regression test around the restore module lifecycle.

Confidence: high

Scope-risk: narrow

Tested: Not run locally per repository policy.

Not-tested: CI execution of the new XCTest.
The restore preload is referenced by inherited NODE_OPTIONS, so it must live in storage with the same lifetime as the app session. The wrapper, Swift CLI, and remote daemon now write the module under Application Support-style cmux/node-options storage and still rewrite it on every launch path before NODE_OPTIONS is emitted.

Application Support contains a space on macOS, so the generated --require value is quoted for Node's NODE_OPTIONS parser. The resume sanitizers now tokenize quoted NODE_OPTIONS values and strip both the old TMPDIR path and the new durable path.

Constraint: macOS temp directories can be cleaned while Claude and child Node processes are still alive.

Constraint: Local test suites are not run in this repo; verification uses syntax/static checks plus the required tagged reload build.

Rejected: Caches directory | still OS-managed and can be purged under pressure.

Rejected: Inline --eval bootstrap | broader launch-contract change than needed once durable storage and self-healing writes are in place.

Confidence: high

Scope-risk: moderate

Directive: Do not move NODE_OPTIONS preload files back under TMPDIR or another purgeable directory.

Tested: bash -n Resources/bin/claude; python3 -m py_compile tests/test_claude_wrapper_hooks.py tests/test_cli_claude_teams_env.py; git diff --check

Not-tested: Swift/XCTest, Python integration, and Go tests locally per repository policy.
@vercel

vercel Bot commented May 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 19, 2026 2:05pm
cmux-staging Building Building Preview, Comment May 19, 2026 2:05pm

@coderabbitai

coderabbitai Bot commented May 5, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Move NODE_OPTIONS restore-module creation from ephemeral TMPDIR into a durable per-user Application Support directory and centralize tokenization, quoting, and restore-path detection into a new CMUXNodeOptions package used by Swift, Go, and the shell wrapper; update tests for quoted paths and durable restore behavior.

Changes

NODE_OPTIONS Durability & Tokenization Refactor

Layer / File(s) Summary
Package / Data Shape
Packages/CMUXNodeOptions/Package.swift, Packages/CMUXNodeOptions/Sources/CMUXNodeOptions/NodeOptionsSupport.swift
Add CMUXNodeOptions library exposing restore filename, claude restore directory computation, quoting helpers, tokenization (tokens/joinedTokens), and restore-module path detection (isCmuxRestoreModulePath).
Core Implementation (Swift CLI & Session)
CLI/cmux.swift, Sources/RestorableAgentSession.swift
Use NodeOptionsSupport.claudeRestoreDirectory(...) for restore module placement; build --require via requirePath(...); replace ad-hoc token logic with NodeOptionsSupport.tokens(...)/joinedTokens(...); use isCmuxRestoreModulePath(...) for sanitization.
Core Implementation (Go agent)
daemon/remote/cmd/cmuxd-remote/agent_launch.go
Write restore module under per-user config dir (os.UserConfigDir()/cmux/node-options); replace strings.Fields with nodeOptionsTokens tokenizer; add nodeOptionsRequirePath, quoteNodeOptionsToken, and joinNodeOptionsTokens; mergeNodeOptions now emits quoted --require=... and normalized heap flag.
Core Implementation (Shell wrapper)
Resources/bin/claude
Add node_options_restore_dir(), node_options_split(), token quoting/joining helpers, and node_options_require_flag(); merge_node_options/normalize_node_options_for_restore now use shell-aware tokenization and produce quoted --require=... plus normalized flags.
Tests / Integration
cmuxTests/*, cmuxTests/ClaudeWrapperNodeOptionsRestoreModuleTests.swift, cmuxTests/AgentResumeNodeOptionsTests.swift, tests/test_claude_wrapper_hooks.py, tests/test_cli_claude_teams_env.py, daemon/.../tmux_compat_test.go
Add unit and integration tests asserting tokenization, quoted-path preservation, durable restore-module creation under ~/Library/Application Support/cmux/node-options, correct --require quoting, and updated wrapper/agent behavior; Python tests use shlex helpers; Go tests add quoted-path cases.
Build / Wiring
Package.swift, GhosttyTabs.xcodeproj/project.pbxproj
Add local Swift package CMUXNodeOptions and wire it into cmux executable and test targets; register two new Swift test sources in the test target.
Cleanup
cmuxTests/SessionPersistenceTests.swift
Remove obsolete TMPDIR-based tests that assumed ephemeral restore-module placement.
sequenceDiagram
participant Wrapper as claude wrapper (shell)
participant CLI as cmux CLI (Swift)
participant Daemon as cmuxd agent (Go)
participant FS as Filesystem (Application Support)
participant Child as Node child processes

Wrapper->>FS: ensure restore module exists under ~/Library/Application Support/cmux/node-options
CLI->>FS: write restore-node-options.cjs via NodeOptionsSupport.claudeRestoreDirectory(...)
Daemon->>FS: ensure restore module via claudeNodeOptionsRestoreDir()
Wrapper->>Wrapper: tokenize NODE_OPTIONS (shlex/tokenizer)
CLI->>CLI: tokenize/join via NodeOptionsSupport.tokens/joinedTokens
Daemon->>Daemon: tokenize/join via nodeOptionsTokens/joinNodeOptionsTokens
Wrapper->>Child: spawn child with NODE_OPTIONS="--require=<quoted restore> --max-old-space-size=4096 ..."
Child->>FS: require restore-node-options.cjs (durable path)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • manaflow-ai/cmux#2978: Touches agent/session restore and NODE_OPTIONS parsing/restore-module handling; closely related.
  • manaflow-ai/cmux#2462: Also modifies NODE_OPTIONS restore/injection flow and quoting/tokenization logic.

Poem

🐰
From tmp's quick nest I softly creep,
To Application Support where files can sleep.
Tokens tamed, quotes neat and bright,
Restore module rests through every night.
Hooray—no more vanished fright!

🚥 Pre-merge checks | ✅ 12 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: moving the Claude NODE_OPTIONS restore module from temp directory cleanup to a durable location.
Linked Issues check ✅ Passed All code changes directly address the objectives in issue #3512: moving restore module from TMPDIR to Application Support, making it self-healing, updating sanitizers for both paths, and improving NODE_OPTIONS quoting.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #3512: Swift package addition, path relocation, NODE_OPTIONS parsing improvements, and related test updates. No unrelated modifications detected.
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor isolation issues. NodeOptionsSupport enum is a pure utility with only static functions and no mutable state. Modified code uses new helpers without isolation problems.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization added to production Swift. NodeOptionsSupport is string parsing only. RestorableAgentSession uses it without semaphores or sleeps. Tests use allowed scaffolding.
Cmux Swift Concurrency ✅ Passed No problematic async patterns in production code. New utilities are pure synchronous. Test code's Dispatch usage is acceptable for subprocess synchronization.
Cmux Swift @Concurrent ✅ Passed All new/modified Swift code is synchronous: NodeOptionsSupport enum, CLI/cmux.swift utility methods, and test methods. No nonisolated async, invalid @concurrent, or actor-isolation issues detected.
Cmux Swift File And Package Boundaries ✅ Passed CMUXNodeOptions package (106 lines) focuses on NODE_OPTIONS. CLI/cmux (-4 net) and RestorableAgentSession (-9 net) both shrank extracting logic. No violations.
Cmux Swift Logging ✅ Passed PR Swift changes comply with swift-logging.md: no print/NSLog/debugPrint/dump in production NODE_OPTIONS code, no Logger imports in NodeOptionsSupport.swift, no secrets exposure.
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI code. All Swift changes are in CLI/backend utilities (session management, node options parsing). SwiftUI state layout check is not applicable.
Cmux Architecture Rethink ✅ Passed No timing, mutable state, observers, or duplicate entrypoints. Pure utility consolidation moving restore module to durable location. Small correctness fix with clear ownership—allowed exception.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required sections: Summary (what changed and why), Testing (multiple testing approaches documented), and a Checklist. The description clearly articulates the problem, solution, and validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-3512-node-options-tmpdir

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Moves restore-node-options.cjs from $TMPDIR into a durable per-user Library/Application Support/cmux/node-options location across the shell wrapper, Swift CLI, and remote Go daemon, fixing a lifecycle mismatch where macOS could clean the temp file while a Claude session was still running. Introduces the shared CMUXNodeOptions Swift package to consolidate quote-aware tokenizing, cmux-entry stripping, and path detection that were previously duplicated across cmux.swift and RestorableAgentSession.swift.

  • Bug fix (durable path): All three launchers now prefer Application Support and fall back to a UID-namespaced, 0700 temp root. The restore module is recreated on every launch so a deleted file self-heals without a restart.
  • Sanitizer correctness: CMUX_ORIGINAL_NODE_OPTIONS_PRESENT is now only set to 1 when the normalized original is non-empty, and the heap-cap strip is now paired to the cmux --require token rather than being unconditional — preserving user-supplied --max-old-space-size values.
  • Test coverage: New Swift unit tests cover the durable-path selection, stale-preload recreation, and resume-time sanitization; Go and Python tests cover the parallel daemon and bash-wrapper paths.

Confidence Score: 5/5

Safe to merge; the durable-path change is well-contained and the self-healing recreate-on-launch guard prevents the class of breakage that motivated this PR.

The implementation is consistent across all three launchers. The heap-cap removal that was previously unconditional is now correctly paired to the cmux-injected require token, preserving user-supplied values. Directory preparation includes symlink checks and writability probes. The shared CMUXNodeOptions package consolidates the production tokenizer. Regression tests cover durable-path selection, stale-preload recreation, quoted paths, and resume-time sanitization.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CMUXNodeOptions/Sources/CMUXNodeOptions/NodeOptionsSupport.swift New shared package; correctly implements quote-aware tokenizer, paired heap-cap removal, isCmuxRestoreModulePath, and secure directory preparation with symlink and ownership checks.
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchEnvironmentPolicy.swift Resume sanitizer now delegates NODE_OPTIONS normalization to CMUXNodeOptions; selectedNodeOptions logic correctly handles the PRESENT flag and empty-original case.
CLI/cmux.swift Removes local heap-cap-strip and tokenizer implementations; now delegates to CMUXNodeOptions; CMUX_ORIGINAL_NODE_OPTIONS_PRESENT=1 is only set when normalizedNodeOptionsForRestore returns a non-nil value.
Resources/bin/claude Bash wrapper gains quote-aware tokenizer, durable restore-dir selection with UID-namespaced temp fallback, and paired heap-cap removal; CMUX_ORIGINAL_NODE_OPTIONS_PRESENT logic now matches Swift/Go.
daemon/remote/cmd/cmuxd-remote/agent_launch.go Go daemon parallels the Swift/bash approach: durable path via os.UserConfigDir(), UID-namespaced temp fallback, quote-aware tokenizer, and paired heap-cap removal.

Reviews (41): Last reviewed commit: "Align NODE_OPTIONS restore normalization..." | Re-trigger Greptile

Comment thread CLI/cmux.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLI/cmux.swift`:
- Around line 11782-11811: The file exceeds the allowed size budget because
three new pure-logic helpers (claudeNodeOptionsRestoreDirectory,
nodeOptionsRequirePath, nodeOptionsTokens) and a parallel NODE_OPTIONS tokenizer
in RestorableAgentSession duplicate reusable logic; extract these helpers and
the tokenizer into a new SwiftPM package target (e.g., SharedNodeOptions or
AgentUtils) and make CLI/cmux.swift and Sources/RestorableAgentSession.swift
depend on that package: move the implementations of
claudeNodeOptionsRestoreDirectory, nodeOptionsRequirePath, nodeOptionsTokens and
the NODE_OPTIONS tokenizer into the package, update both callers to import the
new module, and remove the duplicated code from the app target so CI file-length
budget is satisfied.
- Around line 15396-15406: The function nodeOptionsRequirePath currently builds
charactersRequiringQuotes from whitespace, backslash and double-quote only, so
paths containing a single-quote (apostrophe) are not wrapped and later break
tokenization; update nodeOptionsRequirePath to include the single-quote
character (') in the charactersRequiringQuotes set so any path with an
apostrophe is quoted, keep the existing escaping logic (no special escaping
needed for a literal apostrophe inside the surrounding double quotes) and return
the quoted/escaped string as before to ensure nodeOptionsTokens and
isInjectedNodeOptionsRequire continue to work correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 090e0d61-73a8-498a-87d3-2c9cf4accf6d

📥 Commits

Reviewing files that changed from the base of the PR and between 13d7933 and ce718c7.

📒 Files selected for processing (9)
  • CLI/cmux.swift
  • Resources/bin/claude
  • Sources/RestorableAgentSession.swift
  • cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
  • cmuxTests/SessionPersistenceTests.swift
  • daemon/remote/cmd/cmuxd-remote/agent_launch.go
  • daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go
  • tests/test_claude_wrapper_hooks.py
  • tests/test_cli_claude_teams_env.py
👮 Files not reviewed due to content moderation or server errors (7)
  • cmuxTests/SessionPersistenceTests.swift
  • daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go
  • daemon/remote/cmd/cmuxd-remote/agent_launch.go
  • Sources/RestorableAgentSession.swift
  • Resources/bin/claude
  • cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
  • tests/test_cli_claude_teams_env.py

Comment thread CLI/cmux.swift
Comment thread CLI/cmux.swift Outdated
CodeRabbit and the workflow guard both flagged that the fix duplicated NODE_OPTIONS tokenization between the CLI and app resume path while also pushing CLI/cmux.swift over its size budget. Move the shared parsing, quoting, durable restore-directory resolution, and restore-module path detection into a small SwiftPM package consumed by the app, CLI, and regression tests. Also quote restore paths containing apostrophes so tokenizer round-trips remain valid.

Constraint: CI enforces Swift file/package boundary budgets for CLI/cmux.swift.
Rejected: Keep a second private tokenizer in RestorableAgentSession.swift | this leaves future parser fixes split across production targets.
Confidence: high
Scope-risk: narrow
Directive: Keep NODE_OPTIONS parsing and restore-module path detection in CMUXNodeOptions when adding new Swift callers.
Tested: bash -n Resources/bin/claude; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; python3 -m py_compile tests/test_claude_wrapper_hooks.py tests/test_cli_claude_teams_env.py; swift package describe --package-path Packages/CMUXNodeOptions; swift package describe --type json; git diff --check
Not-tested: Local Swift, Go, and Python integration tests per repository policy; CI will run them.
The workflow guard tracks growth in large Swift files, and the new regressions crossed two existing file budgets. Split the wrapper and resume NODE_OPTIONS coverage into focused test files under the threshold while leaving the behavior assertions unchanged.

Constraint: workflow-guard-tests enforces .github/swift-file-length-budget.tsv.
Confidence: high
Scope-risk: narrow
Directive: Add new regression coverage in focused files when adjacent test files are already budget-constrained.
Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; git diff --check
Not-tested: Local Swift test execution per repository policy; CI will run the test suite.
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 5, 2026 01:47

Requested changes were addressed in 67aaf4a and 4d14ef9; the inline CodeRabbit threads are resolved and the latest CodeRabbit check is green.

Comment thread daemon/remote/cmd/cmuxd-remote/agent_launch.go
Quoted NODE_OPTIONS values can contain whitespace and words that look like flags. The launchers need to tokenize the environment value as an option string, not as raw whitespace fields, before filtering their own heap cap.

Constraint: Review feedback identified the Go and bash launch paths as still using whitespace splitting.

Rejected: Treat Application Support as a special case | quoted NODE_OPTIONS can contain arbitrary user paths and escaped characters.

Confidence: high

Scope-risk: narrow

Directive: Keep NODE_OPTIONS filtering quote-aware in every launcher surface.

Tested: Not run locally per repository testing policy.
The Go remote daemon and bash wrapper now parse NODE_OPTIONS with quote and backslash awareness before filtering cmux's heap cap, then re-quote tokens when writing the value back. This keeps existing quoted require paths intact across every launcher surface.

Constraint: Cursor Bugbot found the non-Swift launchers still used whitespace splitting after the Swift path moved to quote-aware tokenization.

Rejected: Special-case Application Support paths | NODE_OPTIONS can contain arbitrary quoted user paths, so the parser owns the invariant.

Confidence: high

Scope-risk: narrow

Directive: Do not use strings.Fields or read -a for NODE_OPTIONS filtering; preserve quoted option tokens by construction.

Tested: gofmt; bash -n Resources/bin/claude; python3 -m py_compile tests/test_claude_wrapper_hooks.py; git diff --check

Not-tested: Runtime Go/Python regression tests not run locally per repository testing policy.
Comment thread GhosttyTabs.xcodeproj/project.pbxproj Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Packages/CMUXNodeOptions/Sources/CMUXNodeOptions/NodeOptionsSupport.swift`:
- Around line 84-92: The function isCmuxRestoreModulePath currently uses
path.contains(...) which matches substrings and can false-positive; change it to
inspect trailing path components instead. After confirming lastPathComponent ==
restoreModuleFilename, build the standardized URL's pathComponents (from the
same URL used to get path) and check that the components' suffix equals the
exact sequences for managed installs (e.g. ["cmux-claude-node-options",
restoreModuleFilename] or ["cmux","node-options", restoreModuleFilename]) rather
than using contains; adjust isCmuxRestoreModulePath to return true only when one
of those suffix matches.

In `@Resources/bin/claude`:
- Around line 126-135: The node_options_restore_dir function returns
CMUX_NODE_OPTIONS_RESTORE_DIR verbatim which can be relative or start with ~ and
cause MODULE_NOT_FOUND; modify node_options_restore_dir to expand a leading ~ to
$HOME and then convert any non-absolute path into an absolute path (e.g. via
realpath/readlink -f if available, otherwise prefix with the current working
directory and normalize) before trimming a trailing slash and returning it; if
expansion/absolutization fails, return non-zero so callers know the override is
invalid.

In `@tests/test_claude_wrapper_hooks.py`:
- Around line 47-51: The function restore_require_and_remaining uses " ".join
which loses original quoting (causing split_node_options to mis-parse paths with
spaces); change restore_require_and_remaining to reconstitute the remaining
flags using shlex.join (or equivalent shlex.quote join) instead of " ".join so
quoted tokens keep their quotes; update references to split_node_options and any
callers expecting the original quoting preserved (e.g., in tests like
test_live_socket_preserves_quoted_existing_require_path) to use the output from
restore_require_and_remaining unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a4f9be5-9242-4081-9c7b-fe840a511538

📥 Commits

Reviewing files that changed from the base of the PR and between ce718c7 and c3af32b.

📒 Files selected for processing (13)
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Package.swift
  • Packages/CMUXNodeOptions/Package.swift
  • Packages/CMUXNodeOptions/Sources/CMUXNodeOptions/NodeOptionsSupport.swift
  • Resources/bin/claude
  • Sources/RestorableAgentSession.swift
  • cmuxTests/AgentResumeNodeOptionsTests.swift
  • cmuxTests/ClaudeWrapperNodeOptionsRestoreModuleTests.swift
  • cmuxTests/SessionPersistenceTests.swift
  • daemon/remote/cmd/cmuxd-remote/agent_launch.go
  • daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go
  • tests/test_claude_wrapper_hooks.py
💤 Files with no reviewable changes (1)
  • cmuxTests/SessionPersistenceTests.swift

Comment thread Resources/bin/claude
Comment thread tests/test_claude_wrapper_hooks.py Outdated
The follow-up review found a few remaining boundary issues around restore-path classification, override normalization, test-side quoting, and the Xcode package link. This commit tightens those boundaries without changing the durable Application Support lifecycle decision.

Constraint: Post-CI CodeRabbit and Cursor reviews requested these changes before merge
Rejected: Leave override paths verbatim | relative or tilde paths can still produce MODULE_NOT_FOUND under a different child cwd
Confidence: high
Scope-risk: narrow
Directive: Keep restore module detection component-based and keep CMUXNodeOptions linked only to targets that import it
Tested: bash -n Resources/bin/claude; python3 -m py_compile tests/test_claude_wrapper_hooks.py; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; swift package describe --package-path Packages/CMUXNodeOptions; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; git diff --check
Not-tested: Runtime/unit tests not run locally per repository testing policy
Comment thread Resources/bin/claude
Comment thread tests/test_cli_claude_teams_env.py Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/ClaudeWrapperNodeOptionsRestoreModuleTests.swift`:
- Around line 162-185: The helper bindUnixSocket(at:) currently uses XCTAssert*
checks (socket, path length, bindResult, Darwin.listen) which only record
failures and allow execution to continue; change these to throwing guards so the
helper fails fast: replace the socket() check on fd with a guard that throws a
descriptive error if fd < 0, validate utf8.count < maxPathLength with a guard
that throws if violated, and after calling bind (bindResult) and Darwin.listen
check their return values with guards that throw on non-zero results; use the
same local names (fd, addr, utf8, maxPathLength, bindResult) and have
bindUnixSocket(at:) propagate the thrown error to callers so tests stop at the
real failure instead of continuing with invalid state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9ef07ce1-47b7-4736-a0d1-3457c374922c

📥 Commits

Reviewing files that changed from the base of the PR and between c3af32b and 0050f72.

📒 Files selected for processing (5)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Packages/CMUXNodeOptions/Sources/CMUXNodeOptions/NodeOptionsSupport.swift
  • Resources/bin/claude
  • cmuxTests/ClaudeWrapperNodeOptionsRestoreModuleTests.swift
  • tests/test_claude_wrapper_hooks.py

Comment thread cmuxTests/ClaudeWrapperNodeOptionsRestoreModuleTests.swift Outdated
The latest review pass found two test-support issues: one helper still rejoined shell tokens without quoting, and the Unix socket setup helper could keep running after a setup failure. This keeps the regression scaffolding deterministic without changing production behavior.

Constraint: Post-CI Cursor and CodeRabbit feedback requested these fixes before launch/merge.
Rejected: Leave the XCTest assertions in setup code | assertion failures do not stop helper execution and can obscure the real setup error.
Confidence: high
Scope-risk: narrow
Directive: Keep NODE_OPTIONS test helpers quote-aware across Python test files.
Tested: python3 -m py_compile tests/test_cli_claude_teams_env.py tests/test_claude_wrapper_hooks.py; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv; git diff --check; rg '" "\.join\(tokens\[1:\]\)' tests
Not-tested: Runtime/unit tests not run locally per repository testing policy
@austinywang
austinywang dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] May 5, 2026 02:37

Dismissed after all CodeRabbit actionable comments from this stale review were addressed and resolved in later commits; latest CodeRabbit check is passing on 09f3a01.

The NODE_OPTIONS package and the Rovo/Pasteboard package work landed on parallel branches and touched the same SwiftPM and Xcode project sections. The conflict resolution keeps the build graph target-scoped instead of choosing one side: SwiftPM receives both local packages, the app target keeps NodeOptions plus the Rovo/Pasteboard products, and both NodeOptions and Rovo test sources remain registered.

Constraint: PR must merge current origin/main without dropping either branch's package products or tests

Rejected: Accept either side of the project file conflict | would silently unlink the other branch's package/test additions

Confidence: high

Scope-risk: narrow

Directive: Resolve future package metadata conflicts by target ownership and product union, not by side selection

Tested: plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; swift package describe; git diff --check; git diff --cached --check; python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Not-tested: Local Swift test suite per repository policy; CI will run tests

Co-authored-by: OmX <omx@oh-my-codex.dev>
coderabbitai[bot]
coderabbitai Bot previously requested changes May 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Resources/bin/claude`:
- Around line 190-225: The node_options_restore_dir function allows
CMUX_NODE_OPTIONS_RESTORE_DIR to be an arbitrary path which can produce a
restore file without the expected suffix, so change the logic in
node_options_restore_dir (the function handling CMUX_NODE_OPTIONS_RESTORE_DIR)
to ensure the returned directory always ends with a sanitizer-recognized suffix:
append or replace the tail so it ends with either "cmux/node-options"
(preferred) or the legacy "cmux-claude-node-options" if needed; perform this
normalization after expanding tildes and converting to an absolute path, strip
trailing slashes as currently done, and then ensure the final printed value
includes the required suffix so the resume/environment sanitizers can reliably
identify and strip the injected restore preloads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3a3fab2-44b6-4661-ac4e-9a59fd08b01f

📥 Commits

Reviewing files that changed from the base of the PR and between 0050f72 and 0b5cddb.

📒 Files selected for processing (9)
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Package.swift
  • Resources/bin/claude
  • Sources/RestorableAgentSession.swift
  • cmuxTests/ClaudeWrapperNodeOptionsRestoreModuleTests.swift
  • cmuxTests/SessionPersistenceTests.swift
  • tests/test_claude_wrapper_hooks.py
  • tests/test_cli_claude_teams_env.py
💤 Files with no reviewable changes (1)
  • cmuxTests/SessionPersistenceTests.swift

Comment thread Resources/bin/claude
Comment thread cmux.xcodeproj/project.pbxproj
Comment thread Resources/bin/claude Outdated
Comment thread Resources/bin/claude

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 15 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic

Comment thread Resources/bin/claude Outdated
Comment thread daemon/remote/cmd/cmuxd-remote/agent_launch.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1edefd8. Configure here.

Comment thread Packages/CMUXNodeOptions/Sources/CMUXNodeOptions/NodeOptionsSupport.swift Outdated

This branch was successfully deployed

1 active deployment
Preview – cmux — 4a3aac38 Deployed May 19, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NODE_OPTIONS restore module in $TMPDIR breaks after macOS temp cleanup

3 participants