Skip to content

Fix stale Claude NODE_OPTIONS restore preload - #3966

Closed
austinywang wants to merge 21 commits into
mainfrom
issue-3463-node-options-require-tmpdir-stale
Closed

austinywang wants to merge 21 commits into
mainfrom
issue-3463-node-options-require-tmpdir-stale

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #3463

Summary

  • store the Claude NODE_OPTIONS restore module under ~/.claude/cmux before falling back to TMPDIR
  • apply the same persistent restore-module location to the shell wrapper, Swift CLI Claude Teams/OMC path, and remote daemon helper
  • add regression coverage for inherited NODE_OPTIONS after simulated macOS session TMPDIR cleanup

Verification

  • Reproduced before the fix with a fake live cmux socket and fake Claude binary: wrapper exported --require under session TMPDIR, deleting that TMPDIR made child node exit 1 before user code
  • Verified after the fix with the same harness: wrapper exported --require under fake HOME/.claude/cmux, deleting session TMPDIR still allowed child node to start
  • bash -n Resources/bin/claude
  • git diff --check
  • Local test suites not run per repo policy; CI should run the regression tests

Note

Medium Risk
Touches cross-language agent launch/wrapper logic that rewrites NODE_OPTIONS, so mistakes could break Claude/Node startup or preserve the wrong flags; changes are localized and backed by new regression tests.

Overview
Fixes stale/inherited Claude NODE_OPTIONS preloads by persisting the restore module under ~/.claude/cmux (with TMPDIR fallback, and skipping HOME paths that are empty/whitespace/contain whitespace) across the Swift CLI (claude-teams/omc), the shell claude wrapper, and cmuxd-remote.

Refactors NODE_OPTIONS handling to sanitize prior cmux injections: detects --require/-r (including inline =... and quoted paths), drops stale restore-module preloads plus only the paired injected --max-old-space-size=4096, normalizes space-separated heap flags to --max-old-space-size=…, and only exports CMUX_ORIGINAL_NODE_OPTIONS when non-empty.

Adds/updates regression tests covering resume-command stripping, home/TMPDIR fallback behavior, persistence after TMPDIR cleanup, and stale-preload cleanup in Swift, Go, and Python harnesses.

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


Summary by cubic

Persist the Claude NODE_OPTIONS restore preload under ~/.claude/cmux with TMPDIR fallback and whitespace-HOME safeguards, and sanitize inherited NODE_OPTIONS to strip cmux restore preloads and only the cmux-injected heap cap while preserving user heap flags; fixes #3463.

  • Bug Fixes

    • Sanitize NODE_OPTIONS across the Swift CLI (incl. claude-teams), the shell claude wrapper (at startup and before passthrough), and cmuxd-remote: remove cmux restore preloads from TMPDIR or ~/.claude/cmux, handle --require/-r and inline =... (with quotes), drop only the paired injected --max-old-space-size=4096, normalize heap flags to =, and set CMUX_ORIGINAL_NODE_OPTIONS only when non-empty.
    • Prefer a persistent restore module at ~/.claude/cmux with TMPDIR fallback; ignore empty or whitespace-containing HOME; survive session TMPDIR cleanup. Tests cover home/TMPDIR placement and fallback, persistence after temp cleanup, inherited stale-preload cleanup (launcher/runtime/child and passthrough), and cmuxd-remote behavior.
  • Refactors

    • Share a single NODE_OPTIONS walker and restore logic across Swift, Bash, and Go.

Written for commit 9c71c85. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes & Improvements

    • Prefer installing the restore preload under a persistent home location (~/.claude/cmux) with robust fallback to temp dirs and atomic writes.
    • Better sanitize NODE_OPTIONS: strip wrapper-injected preload and its injected heap-cap, normalize heap-cap forms, and only export original options when non-empty.
  • Tests

    • Added/expanded tests covering restore-module placement, HOME/TMPDIR fallbacks, resume-command stripping, persistence after cleanup, and NODE_OPTIONS normalization.

Review Change Stack

@vercel

vercel Bot commented May 12, 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 22, 2026 2:03am
cmux-staging Building Building Preview, Comment May 22, 2026 2:03am

@coderabbitai

coderabbitai Bot commented May 12, 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

Places restore-node-options.cjs in the first writable candidate directory (prefer HOME/.claude/cmux), normalizes/sanitizes NODE_OPTIONS by removing injected restore-module requires and associated heap caps, and updates tests and path recognition across Swift, Bash, Go, and Python.

Changes

Persistent NODE_OPTIONS restore-module

Layer / File(s) Summary
Swift restore-module placement & normalization
CLI/cmux.swift
Try multiple candidate dirs for restore shim, write shim atomically to first writable candidate, set CMUX_ORIGINAL_NODE_OPTIONS only when normalized value is non-nil, and refine token normalization to drop injected restore-module --require and adjacent heap-cap tokens.
Agent path recognition
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchEnvironmentPolicy.swift
Recognize /.claude/cmux/ patterns in addition to /cmux- when detecting restore-node-options.cjs paths for sanitization.
Bash wrapper normalization & persistent write
Resources/bin/claude
Add restore-module path detection and inline --require=... parsing, expand normalize_node_options_for_restore() to tokenize and strip restore requires and tied --max-old-space-size flags, sanitize inherited NODE_OPTIONS (unset when empty), generate candidate guard dirs (HOME-first), add atomic write/install helper, and normalize before merging restore-module and heap flags.
Go daemon placement & NODE_OPTIONS parsing
daemon/remote/cmd/cmuxd-remote/agent_launch.go
Return first successful write across candidate dirs, add claudeNodeOptionsRestoreDirs, and add token-level walkNodeOptions with nodeOptionsForRestore to detect/remove restore requires and adjacent heap-cap tokens used by configureClaudeNodeOptions.
Go tmux compatibility tests
daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go
Adds/extends tests verifying heap-flag preservation, dropping stale/persistent restore --require entries, and HOME-preference for restore-module placement.
Python wrapper harness and tmpdir tests
tests/test_claude_wrapper_hooks.py
Adds optional home to run_wrapper, creates fake_home, adds helper to extract --require from NODE_OPTIONS, replaces tmpdir-skip test with HOME-based injection test, adds whitespace-HOME fallback and persistence tests, and adds live/stale socket stripping tests; updates main().
CLI claude-teams integration tests
tests/test_cli_claude_teams_env.py
Update expectations for equals-form heap flags, persistent HOME-based restore preload when TMPDIR is unusable, and stripping stale tmpdir preload paths in launcher/runtime/child NODE_OPTIONS.
Swift resume-command test
cmuxTests/SessionPersistenceTests.swift
Adds test ensuring resumeCommand strips persistent restore-node-options.cjs --require while preserving other NODE_OPTIONS flags.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#2462: Modifies the same restore-module / NODE_OPTIONS normalization and merge behavior across CLI, wrapper, and daemon code paths.

Poem

🐰 I burrowed through HOME and TMPDIR tonight,
I planted the shim where it stays out of sight.
NODE_OPTIONS cleaned, no stale preload in view,
Swift, Bash, Go, and tests all checked it true.
Hooray — the restore snack will stick like glue!


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift File And Package Boundaries ❌ Error NODE_OPTIONS helper functions duplicated in CLI/cmux.swift (+101 net) and CMUXAgentLaunch package. Reusable logic across surfaces must consolidate behind package boundaries, not duplicate. Extract NODE_OPTIONS helpers to shared location or import from CMUXAgentLaunch. Reduces CLI/cmux.swift duplication by ~60 lines.
Cmux User-Facing Error Privacy ❌ Error User-facing error exposes environment variable name "NODE_OPTIONS" in stderr output, violating the user-facing-errors.md rule against exposing environment variable names. Replace "NODE_OPTIONS" with a generic term in the warning at daemon/remote/cmd/cmuxd-remote/agent_launch.go. Use "Node.js options" or similar instead of the environment variable name.
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 (13 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main fix: preventing stale NODE_OPTIONS restore preload paths by persisting them instead of using temporary directories.
Linked Issues check ✅ Passed The changes directly address all coding requirements from issue #3463: persist restore module to ~/.claude/cmux with TMPDIR fallback, sanitize inherited NODE_OPTIONS across Swift/Bash/Go, handle whitespace-only HOME, and add regression test coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to the issue objectives: Swift CLI, shell wrapper, and daemon modifications to restore preload persistence and NODE_OPTIONS sanitization; plus aligned test coverage across multiple frameworks.
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor isolation issues introduced. Changes are in private functions or existing public static functions performing pure string manipulation without isolation concerns. Tests are exempt.
Cmux Swift Blocking Runtime ✅ Passed NODE_OPTIONS restoration functions use no blocking synchronization. String/file operations only; no semaphores, locks, waits, or sleeps.
Cmux No Hacky Sleeps ✅ Passed No hacky sleeps, timers, polling, or timing patterns found. Production runtime changes contain only deterministic NODE_OPTIONS path handling and token manipulation—no prohibited timing hacks.
Cmux Swift Concurrency ✅ Passed All Swift code changes in the PR use synchronous patterns. No legacy async patterns (DispatchQueue, Combine, fire-and-forget Tasks, or completion handlers) are introduced or expanded.
Cmux Swift @Concurrent ✅ Passed All Swift changes are to synchronous private functions in non-actor structs/enums. No async functions, no @concurrent violations, no isolation issues.
Cmux Swift Logging ✅ Passed Swift production code changes comply with logging rules: no print/debugPrint/dump/NSLog in new/modified functions; no ad hoc diagnostics; no Logger rule violations; no secrets exposed.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state layout violations found. Modified Swift files contain no SwiftUI imports or view patterns. Changes are NODE_OPTIONS handling and session persistence logic only.
Cmux Architecture Rethink ✅ Passed Private-only Swift changes with no timing, blocking, locks, observers, side channels, or split ownership. Clear invariants for NODE_OPTIONS restore module persistence.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No NSWindow, NSPanel, or WindowGroup instantiation in Swift changes. Changes are utility functions and test-only code, which are exempt from window identifier requirements.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering the summary, verification, and testing approach. However, it lacks a dedicated Testing section matching the template structure and misses explicit checklist items.
✨ 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-3463-node-options-require-tmpdir-stale

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 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a macOS session-scoped TMPDIR cleanup race (#3463) by persisting the Claude NODE_OPTIONS restore preload module under ~/.claude/cmux with a TMPDIR fallback, ensuring the module survives macOS garbage-collecting the per-session temp directory between agent re-launches. It also adds a startup sanitization pass in the shell wrapper that strips stale inherited cmux preloads from NODE_OPTIONS before re-injecting a fresh one, and aligns the same logic across the Swift CLI, cmuxd-remote Go daemon, and bash wrapper.

  • Persistent restore module placement: createClaudeNodeOptionsRestoreModule / ensureClaudeNodeOptionsRestoreModule / claudeNodeOptionsRestoreDirs now try ~/.claude/cmux first (skipping HOME paths with whitespace), falling back to TMPDIR; the change is applied identically in Swift, Go, and bash.
  • Startup sanitization: sanitize_inherited_cmux_node_options runs unconditionally at wrapper startup, stripping any inherited cmux --require preload (temp or home) and its paired injected --max-old-space-size=4096 from NODE_OPTIONS before the wrapper decides whether to inject a new one; the Go daemon gains the same sanitization in configureClaudeNodeOptions.
  • Behavior change in heap-cap handling: merge_node_options / mergeNodeOptions no longer removes user-supplied --max-old-space-size flags; both the user's cap and the cmux-injected 4096 cap are preserved in NODE_OPTIONS (last-wins in Node.js, so the user's setting takes effect), which is confirmed by updated tests.

Confidence Score: 5/5

Safe to merge; all three code paths apply the fix consistently, atomic writes preserve the restore module across concurrent launches, and the regression tests cover the exact macOS TMPDIR-cleanup scenario the fix targets.

The fix is narrow and mechanical — write the restore module to a stable home-directory path before falling back to the session-scoped TMPDIR, and strip stale inherited preloads at startup. The walker logic in all three implementations handles every form of the injected flag and is idempotent. No blocking primitives, actor-isolation issues, or user-facing strings were introduced.

No files require special attention. The one-line change in AgentLaunchEnvironmentPolicy.swift is kept in sync with the identical extension in CLI/cmux.swift.

Important Files Changed

Filename Overview
Resources/bin/claude Core bash wrapper: adds sanitize_inherited_cmux_node_options (runs at startup, strips stale cmux preloads), splits restore-module write into write_node_options_restore_module + node_options_restore_dir_candidates (HOME-first, TMPDIR fallback), and merges normalize_node_options_for_restore with the new stripping logic.
daemon/remote/cmd/cmuxd-remote/agent_launch.go Go daemon: adds claudeNodeOptionsRestoreDirs (HOME-first, TMPDIR fallback), replaces old cleanedNodeOptions with unified walkNodeOptions that strips cmux preloads and normalizes heap flags, updates configureClaudeNodeOptions to strip stale preloads before storing CMUX_ORIGINAL_NODE_OPTIONS and to set PRESENT=0 when normalization yields empty.
CLI/cmux.swift Swift CLI: createClaudeNodeOptionsRestoreModule now tries HOME/.claude/cmux before TMPDIR; walkNodeOptions refactor strips cmux preloads and their paired injected heap cap; normalizedNodeOptionsForRestore returns Optional to distinguish empty-after-normalization from not-set; both claude-teams and OMC call sites updated consistently.
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchEnvironmentPolicy.swift Minimal one-line change: extends isCmuxNodeOptionsRestoreModulePath to also recognize /.claude/cmux/ paths alongside the existing /cmux- check, keeping this package in sync with the CLI changes.
cmuxTests/SessionPersistenceTests.swift Adds testClaudeResumeCommandStripsPersistentCmuxNodeOptionsRestoreModule covering the new /.claude/cmux/ path in resume-command stripping.
daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go Extensive new Go tests: HOME-vs-TMPDIR placement, whitespace HOME fallback, configureClaudeNodeOptions with stale/persistent preloads, and updated mergeNodeOptions expectations reflecting the new heap-cap preservation behavior.
tests/test_claude_wrapper_hooks.py New end-to-end Python tests: whitespace/space HOME fallback, TMPDIR cleanup survival, stale inherited preload stripping for both live-socket and stale-socket passthrough paths; also updates the heap-cap preservation expectations.
tests/test_cli_claude_teams_env.py New tests for claude-teams: space-HOME fallback to TMPDIR, TMPDIR-unusable fallback to HOME/.claude/cmux, stale inherited restore preload stripping; also updates heap-cap preservation expectations.

Reviews (15): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

@austinywang
austinywang force-pushed the issue-3463-node-options-require-tmpdir-stale branch from b0e4272 to 17a02c0 Compare May 12, 2026 06:12
coderabbitai[bot]
coderabbitai Bot previously requested changes May 12, 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 `@CLI/cmux.swift`:
- Around line 12388-12395: The current logic sets homePath from
environment["HOME"] trimmed or NSHomeDirectory(), but then skips adding a
candidate when that trimmed value is empty, causing an explicit HOME="" to
bypass NSHomeDirectory(); update the assignment for homePath so that if
environment["HOME"] exists but is empty/whitespace you fall back to
NSHomeDirectory(), otherwise use the trimmed value (and if environment["HOME"]
is nil also use NSHomeDirectory()); keep the subsequent check and candidate
append to URL(fileURLWithPath: homePath, isDirectory:
true).appendingPathComponent(".claude", isDirectory:
true).appendingPathComponent("cmux", isDirectory: true) intact.
🪄 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: f26ddbcc-9e7a-413c-aba6-326598629a8a

📥 Commits

Reviewing files that changed from the base of the PR and between bf0f982 and 292802f.

📒 Files selected for processing (3)
  • CLI/cmux.swift
  • Resources/bin/claude
  • tests/test_claude_wrapper_hooks.py

Comment thread CLI/cmux.swift Outdated
Comment thread Resources/bin/claude Outdated

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
daemon/remote/cmd/cmuxd-remote/agent_launch.go (1)

420-494: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated token-walking logic between cleanedNodeOptions and nodeOptionsForRestore.

The two functions share an identical ~25-line loop covering the empty-tokens guard, the dropInjectedHeapCap window, restore-module require detection (both space and inline forms), and the final default-append. They only diverge in how --max-old-space-size is handled (strip vs. normalize-and-keep). A future change to require/heap-cap detection (e.g., a new injected flag or path location) will need to be applied to both copies and is easy to miss.

Consider extracting a shared walker parameterized by a heap-cap handler, e.g.:

♻️ Proposed consolidation
type heapCapMode int

const (
    heapCapStrip heapCapMode = iota
    heapCapNormalize
)

func walkNodeOptions(existing string, mode heapCapMode) string {
    tokens := strings.Fields(existing)
    if len(tokens) == 0 {
        return ""
    }
    filtered := make([]string, 0, len(tokens))
    dropInjectedHeapCap := false
    for i := 0; i < len(tokens); i++ {
        token := tokens[i]
        if dropInjectedHeapCap && isInjectedNodeHeapCap(tokens, i) {
            i += nodeHeapCapWidth(tokens, i) - 1
            dropInjectedHeapCap = false
            continue
        }
        dropInjectedHeapCap = false

        if isRequireOption(token) && i+1 < len(tokens) && isCmuxNodeOptionsRestoreModulePath(tokens[i+1]) {
            i++
            dropInjectedHeapCap = true
            continue
        }
        if path, ok := inlineRequireOptionPath(token); ok && isCmuxNodeOptionsRestoreModulePath(path) {
            dropInjectedHeapCap = true
            continue
        }
        switch mode {
        case heapCapStrip:
            if token == "--max-old-space-size" {
                if i+1 < len(tokens) {
                    i++
                }
                continue
            }
            if strings.HasPrefix(token, "--max-old-space-size=") {
                continue
            }
        case heapCapNormalize:
            if token == "--max-old-space-size" && i+1 < len(tokens) {
                filtered = append(filtered, "--max-old-space-size="+tokens[i+1])
                i++
                continue
            }
        }
        filtered = append(filtered, token)
    }
    return strings.Join(filtered, " ")
}

func cleanedNodeOptions(existing string) string   { return walkNodeOptions(existing, heapCapStrip) }
func nodeOptionsForRestore(existing string) string { return walkNodeOptions(existing, heapCapNormalize) }
🤖 Prompt for 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.

In `@daemon/remote/cmd/cmuxd-remote/agent_launch.go` around lines 420 - 494, The
two functions cleanedNodeOptions and nodeOptionsForRestore duplicate the same
token-walking loop; extract that common logic into a single
walkNodeOptions(existing string, mode heapCapMode) function (define heapCapMode
with values like heapCapStrip and heapCapNormalize) and move the shared
empty-tokens guard, dropInjectedHeapCap handling, require/inline require
detection, and default append into it; inside the walker branch on mode to
either strip both "--max-old-space-size" forms (heapCapStrip) or normalize the
space form into "--max-old-space-size=NNN" (heapCapNormalize), then make
cleanedNodeOptions and nodeOptionsForRestore thin wrappers that call
walkNodeOptions with the appropriate mode.
🤖 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.

Outside diff comments:
In `@daemon/remote/cmd/cmuxd-remote/agent_launch.go`:
- Around line 420-494: The two functions cleanedNodeOptions and
nodeOptionsForRestore duplicate the same token-walking loop; extract that common
logic into a single walkNodeOptions(existing string, mode heapCapMode) function
(define heapCapMode with values like heapCapStrip and heapCapNormalize) and move
the shared empty-tokens guard, dropInjectedHeapCap handling, require/inline
require detection, and default append into it; inside the walker branch on mode
to either strip both "--max-old-space-size" forms (heapCapStrip) or normalize
the space form into "--max-old-space-size=NNN" (heapCapNormalize), then make
cleanedNodeOptions and nodeOptionsForRestore thin wrappers that call
walkNodeOptions with the appropriate mode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 828d2280-8b56-4e3d-952a-01c758f752b5

📥 Commits

Reviewing files that changed from the base of the PR and between 292802f and bf7ee7c.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Resources/bin/claude
  • 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

coderabbitai[bot]
coderabbitai Bot previously requested changes May 17, 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 `@CLI/cmux.swift`:
- Around line 18999-19023: The loop currently unconditionally drops a split-form
"--max-old-space-size <value>" pair; change that behavior so the pair is only
removed when it was the cmux-injected cap. In the token branch that matches
token == "--max-old-space-size", check shouldDropInjectedHeapCap and call
isInjectedNodeHeapCap(tokens, index: index) (or use nodeHeapCapWidth) to confirm
the following value is the injected cap (e.g., "4096"); if both true, consume
both tokens (index += min(2, tokens.count - index)), clear
shouldDropInjectedHeapCap and continue, otherwise do not consume the value
(advance only past the flag or leave handling to the normal flow) so
user-supplied heap limits are preserved. Ensure you reference and update the
same symbols: shouldDropInjectedHeapCap, isInjectedNodeHeapCap(tokens, index:),
nodeHeapCapWidth(tokens, index:), and the "--max-old-space-size" token check.
🪄 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: 12263ec5-9533-4c42-9a23-a791d42aaf20

📥 Commits

Reviewing files that changed from the base of the PR and between bf7ee7c and 6fbdc44.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchEnvironmentPolicy.swift
  • Resources/bin/claude
  • cmuxTests/SessionPersistenceTests.swift
  • daemon/remote/cmd/cmuxd-remote/agent_launch.go
  • tests/test_claude_wrapper_hooks.py

Comment thread CLI/cmux.swift Outdated
Comment thread tests/test_claude_wrapper_hooks.py
coderabbitai[bot]
coderabbitai Bot previously requested changes May 17, 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CLI/cmux.swift (1)

18997-19029: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Extract the NODE_OPTIONS walker into one helper.

Lines 18997-19029 and Lines 19038-19072 now carry the same stateful token-walk logic in two places. That makes the sanitize-on-launch and sanitize-for-restore paths easy to drift apart the next time this parser changes, which is exactly the class of bug this PR is trying to eliminate. Please have both call sites share a single walker and keep the caller-specific String/String? handling outside it.

Also applies to: 19038-19072

🤖 Prompt for 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.

In `@CLI/cmux.swift` around lines 18997 - 19029, The duplicated stateful
token-walking logic that builds a filtered token list (uses variables tokens,
index, shouldDropInjectedHeapCap and helper checks isInjectedNodeHeapCap,
nodeHeapCapWidth, isRequireOption, inlineRequireOptionPath,
isCmuxNodeOptionsRestoreModulePath) should be extracted into a single helper
function (e.g. walkAndFilterNodeOptions(tokens: [String]) -> [String]) that
encapsulates the while-loop and all token-mutating behavior; replace both
duplicate blocks with calls to this helper and keep any caller-specific
conversion to String or String? (joining with " " or returning nil/optional)
outside the helper so the walker only returns a neutral [String] result for
callers to format as needed. Ensure the helper preserves the exact semantics
around dropping injected heap caps and rewriting "--max-old-space-size" to
"--max-old-space-size=<value>" by using the existing helper predicates
(isInjectedNodeHeapCap, nodeHeapCapWidth, isRequireOption,
inlineRequireOptionPath, isCmuxNodeOptionsRestoreModulePath).
🤖 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 `@daemon/remote/cmd/cmuxd-remote/agent_launch.go`:
- Around line 337-343: claudeNodeOptionsRestoreDirs() can produce a path with
spaces which breaks mergeNodeOptions and walkNodeOptions that use simple
whitespace tokenization; to fix, filter out any candidate paths containing
whitespace in claudeNodeOptionsRestoreDirs() (skip adding home-based path when
strings.ContainsAny(home, " \t\n") is true) so mergeNodeOptions and
walkNodeOptions continue to work unchanged, and add a short comment referencing
mergeNodeOptions and walkNodeOptions so future changes consider quote-aware
parsing if you later need to support spaces.

In `@Resources/bin/claude`:
- Around line 193-244: normalize_node_options_for_restore() breaks paths with
spaces because merge_node_options() emits unquoted --require=$guard_path and
normalize_node_options_for_restore() uses read -r -a (whitespace split). Fix by
either (A) excluding any node_options_restore_dir_candidates() entries that
contain spaces so merge_node_options() never emits those, or (B) make both sides
quote-aware: in merge_node_options() emit the require flag with a shell-safe
quoted value (e.g., use printf '%q' or emit --require="$guard_path" style
quoting when constructing NODE_OPTIONS), and in
normalize_node_options_for_restore() replace the simple read-splitting with a
quote-aware tokenizer (e.g., evaluate the string into an array with shell
word-splitting that preserves quoted segments or use eval to populate tokens) so
--require="path with spaces" stays a single token; update references to
guard_path, merge_node_options, and normalize_node_options_for_restore
accordingly.

In `@tests/test_claude_wrapper_hooks.py`:
- Line 961: The test was renamed to
test_live_socket_preserves_space_separated_heap_cap but main() still calls the
old name test_live_socket_enforces_heap_cap_for_space_separated_flag, causing a
NameError; update the call inside main() to invoke
test_live_socket_preserves_space_separated_heap_cap (or revert the test name)
and ensure any references to the old function name are replaced so the test
runner calls the actual function.

---

Outside diff comments:
In `@CLI/cmux.swift`:
- Around line 18997-19029: The duplicated stateful token-walking logic that
builds a filtered token list (uses variables tokens, index,
shouldDropInjectedHeapCap and helper checks isInjectedNodeHeapCap,
nodeHeapCapWidth, isRequireOption, inlineRequireOptionPath,
isCmuxNodeOptionsRestoreModulePath) should be extracted into a single helper
function (e.g. walkAndFilterNodeOptions(tokens: [String]) -> [String]) that
encapsulates the while-loop and all token-mutating behavior; replace both
duplicate blocks with calls to this helper and keep any caller-specific
conversion to String or String? (joining with " " or returning nil/optional)
outside the helper so the walker only returns a neutral [String] result for
callers to format as needed. Ensure the helper preserves the exact semantics
around dropping injected heap caps and rewriting "--max-old-space-size" to
"--max-old-space-size=<value>" by using the existing helper predicates
(isInjectedNodeHeapCap, nodeHeapCapWidth, isRequireOption,
inlineRequireOptionPath, isCmuxNodeOptionsRestoreModulePath).
🪄 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: ef499cd3-ee31-442c-ac74-ba92824ebfd8

📥 Commits

Reviewing files that changed from the base of the PR and between 6fbdc44 and af8113e.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Resources/bin/claude
  • 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

Comment thread daemon/remote/cmd/cmuxd-remote/agent_launch.go
Comment thread Resources/bin/claude
Comment thread tests/test_claude_wrapper_hooks.py

@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.

1 issue found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_claude_wrapper_hooks.py">

<violation number="1" location="tests/test_claude_wrapper_hooks.py:961">
P2: The test function was renamed but `main()` still calls the old name, which causes a `NameError` and aborts the regression runner.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Re-trigger cubic

Comment thread tests/test_claude_wrapper_hooks.py
@austinywang
austinywang dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] May 18, 2026 06:59

Stale bot review: the heap-cap preservation finding was addressed by af8113e and current HEAD preserves user split-form heap options.

@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 7d9cfc9. Configure here.

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

This branch was successfully deployed

1 active deployment
Preview – cmux — 9c71c853 Deployed May 22, 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 --require path in $TMPDIR becomes stale after macOS temp cleanup

3 participants