Skip to content

fix: re-prompt on invalid sandbox name instead of exiting#1173

Open
BenediktSchackenberg wants to merge 4 commits intoNVIDIA:mainfrom
BenediktSchackenberg:fix/sandbox-name-validation-retry
Open

fix: re-prompt on invalid sandbox name instead of exiting#1173
BenediktSchackenberg wants to merge 4 commits intoNVIDIA:mainfrom
BenediktSchackenberg:fix/sandbox-name-validation-retry

Conversation

@BenediktSchackenberg
Copy link
Copy Markdown
Contributor

@BenediktSchackenberg BenediktSchackenberg commented Mar 31, 2026

Summary

When a user enters an invalid sandbox name (e.g. starting with a digit like 7racii), promptValidatedSandboxName() now shows the error and re-prompts instead of calling process.exit(1).

This prevents users from getting stuck in a loop where rerunning the onboard script reuses the invalid name from the session state, requiring manual deletion of ~/.nemoclaw to recover.

Changes

  • bin/lib/onboard.js: Replace exit-on-invalid with a while(true) retry loop in interactive mode. Non-interactive mode (CI/CD) still exits with status 1.
  • test/onboard.test.js: Source-level test verifying the retry loop exists and non-interactive fallback is preserved.

Before

Sandbox name: 7racii
Invalid sandbox name: '7racii'
Names must be lowercase...
# process.exit(1) — rerun uses same name from session, stuck

After

Sandbox name: 7racii
Invalid sandbox name: '7racii'
Names must be lowercase...
Please try again.

Sandbox name: my-racii
✓ Valid name accepted

Closes #1120

Summary by CodeRabbit

  • Bug Fixes

    • Sandbox name validation now lets interactive users retry after invalid input with a clear "Please try again" prompt; non-interactive runs still terminate on invalid names.
  • Tests

    • Added/updated tests to verify the interactive retry behavior and non-interactive exit, and strengthened temp-file handling assertions.

Add secureTempFile(prefix, ext) helper using fs.mkdtempSync() to
create temp files inside OS-level unique directories, preventing
symlink attacks on predictable /tmp paths.

Add cleanupTempDir(filePath, expectedPrefix) guard that verifies
the parent directory matches the expected mkdtemp prefix before
calling fs.rmSync recursive, preventing accidental deletion of the
system temp root on regression.

Changes:
- runCurlProbe: replace Date.now()+Math.random() with secureTempFile
- writeSandboxConfigSyncFile: use secureTempFile, drop tmpDir param
- All cleanup sites use cleanupTempDir guard
- Test updated for new function signature and mkdtemp assertions

Closes NVIDIA#1093
When a user enters an invalid sandbox name (e.g. starting with a
digit), promptValidatedSandboxName() now shows the error and
re-prompts instead of calling process.exit(1). This prevents users
from getting stuck in a loop where rerunning the onboard script
reuses the invalid name from the session state, requiring manual
deletion of ~/.nemoclaw.

In non-interactive mode (CI/CD), the function still exits with
status 1 since re-prompting is not possible.

Closes NVIDIA#1120
Copilot AI review requested due to automatic review settings March 31, 2026 11:54
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 508e0a3b-cd45-4a61-986a-93caf5bdd41d

📥 Commits

Reviewing files that changed from the base of the PR and between 6129bd4 and e288d08.

📒 Files selected for processing (1)
  • test/onboard.test.js

📝 Walkthrough

Walkthrough

Updated onboarding sandbox-name validation to loop and re-prompt interactively on invalid RFC 1123 subdomain names; in non-interactive mode the script still exits immediately. Tests were updated to reflect retry behavior and to change temporary-file handling assertions.

Changes

Cohort / File(s) Summary
Interactive Validation Retry Loop
bin/lib/onboard.js
promptValidatedSandboxName() now loops (while (true)) and re-prompts on invalid RFC 1123 subdomain input, printing "Please try again."; retains process.exit(1) for non-interactive mode.
Test Coverage & Temp-file Handling
test/onboard.test.js
Adjusted sandbox sync temp-file test to stop supplying an explicit mkdtemp dir, relaxes expected script name regex to nemoclaw-sync.*\.sh, asserts parent-dir naming (nemoclaw-sync in path) and cleans up that parent dir conditionally; added unit test verifying the new retry loop (while (true) + "Please try again") and non-interactive process.exit(1) presence by inspecting bin/lib/onboard.js source.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I nudge the prompt, then hop once more,
RFC gates guard the sandbox door,
If digits trip your first hello,
"Please try again" — retry we go,
A soft loop fixes what was sore.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: re-prompt on invalid sandbox name instead of exiting' directly summarizes the main change: converting from immediate exit to re-prompting on invalid input in interactive mode.
Linked Issues check ✅ Passed The PR successfully addresses #1120 by implementing a retry loop for invalid sandbox names in interactive mode while preserving non-interactive exit behavior, matching the issue's requirement to allow re-entry instead of exiting.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing the linked issue: the retry loop implementation in onboard.js, the source-level test verification, and the sandbox sync script test update for related filesystem handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/onboard.test.js (1)

1578-1588: Source-level test verifies the retry loop exists, but the regex is formatting-sensitive.

The regex /isNonInteractive\(\)\)\s*\{\s*\n\s*process\.exit\(1\)/ on line 1587 is tightly coupled to the current code formatting. If the code is reformatted (e.g., different whitespace between { and process.exit), the test will fail even though the behavior is correct.

Consider a more resilient pattern that allows for any whitespace between the key tokens:

♻️ Suggested more flexible regex
-    assert.match(source, /isNonInteractive\(\)\)\s*\{\s*\n\s*process\.exit\(1\)/);
+    assert.match(source, /isNonInteractive\(\)\)\s*\{[\s\S]*?process\.exit\(1\)/);

Alternatively, consider splitting into two separate assertions (one for the condition, one for the exit call) to make failures more descriptive.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/onboard.test.js` around lines 1578 - 1588, The test's regex that checks
for the non-interactive exit is fragile due to strict whitespace/newline
matching; update the assertion in the "re-prompts on invalid sandbox names..."
test to either (a) use a single, resilient pattern that allows any
whitespace/newline between tokens (e.g., match
isNonInteractive\(\).*process\.exit\(1\) with dot-all or [\s\S]* to span
newlines) or (b) split into two assertions: one asserting the presence of
isNonInteractive() and a second asserting process.exit(1) appears, so the test
no longer depends on exact formatting; update the assertion referencing
isNonInteractive() and process.exit(1) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/onboard.test.js`:
- Around line 1578-1588: The test's regex that checks for the non-interactive
exit is fragile due to strict whitespace/newline matching; update the assertion
in the "re-prompts on invalid sandbox names..." test to either (a) use a single,
resilient pattern that allows any whitespace/newline between tokens (e.g., match
isNonInteractive\(\).*process\.exit\(1\) with dot-all or [\s\S]* to span
newlines) or (b) split into two assertions: one asserting the presence of
isNonInteractive() and a second asserting process.exit(1) appears, so the test
no longer depends on exact formatting; update the assertion referencing
isNonInteractive() and process.exit(1) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e85ef663-ac30-44df-ac4c-6e2b3a858df4

📥 Commits

Reviewing files that changed from the base of the PR and between b34c4f4 and 77465e1.

📒 Files selected for processing (2)
  • bin/lib/onboard.js
  • test/onboard.test.js

Address CodeRabbit nitpick: use [\s\S]*? instead of strict
whitespace/newline matching so the assertion survives reformatting.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the onboarding CLI flow to handle invalid sandbox-name input more gracefully by retrying interactively rather than terminating, addressing the “stuck on resume due to persisted bad name” problem described in #1120.

Changes:

  • Updated promptValidatedSandboxName() to retry on validation failure in interactive mode, while preserving process.exit(1) behavior for non-interactive runs.
  • Adjusted the temp-file test for sandbox sync script writing/cleanup.
  • Added a source-level test asserting the new retry-loop behavior and non-interactive exit behavior remain present.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.

File Description
bin/lib/onboard.js Changes invalid sandbox-name handling to re-prompt interactively instead of exiting.
test/onboard.test.js Updates temp script-file assertions/cleanup and adds a source-regex test for the new retry loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1583 to +1587
// Verify the retry loop exists (while + re-prompt pattern)
assert.match(source, /while\s*\(true\)/);
assert.match(source, /Please try again/);
// Non-interactive still exits
assert.match(source, /isNonInteractive\(\)\)[\s\S]*?process\.exit\(1\)/);
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

This test is very brittle and can produce false positives/negatives:

  • /while\s*\(true\)/ matches many loops in onboard.js and doesn’t specifically assert that promptValidatedSandboxName() is the one that loops.
  • The isNonInteractive()/process.exit(1) regex is not scoped to the sandbox-name branch and hard-codes \n, which can fail on CRLF checkouts.

To make this more reliable, scope the assertions to the promptValidatedSandboxName function body (e.g., extract/match a substring starting at async function promptValidatedSandboxName), and allow \r?\n (or avoid newline literals entirely).

Suggested change
// Verify the retry loop exists (while + re-prompt pattern)
assert.match(source, /while\s*\(true\)/);
assert.match(source, /Please try again/);
// Non-interactive still exits
assert.match(source, /isNonInteractive\(\)\)[\s\S]*?process\.exit\(1\)/);
const functionStart = source.indexOf("async function promptValidatedSandboxName");
assert.notEqual(functionStart, -1);
const promptValidatedSandboxNameSource = source.slice(functionStart);
// Verify the retry loop and re-prompt pattern exist within promptValidatedSandboxName
assert.match(
promptValidatedSandboxNameSource,
/while\s*\(true\)[\s\S]*?Please try again/
);
// Non-interactive still exits within the same function
assert.match(
promptValidatedSandboxNameSource,
/isNonInteractive\(\)[\s\S]*?process\.exit\(1\)/
);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/onboard.test.js (1)

1583-1588: Scope the retry-loop regex checks to the sandbox-name function block.

Current assertions are file-wide and can pass on unrelated while (true) or process.exit(1) occurrences, weakening this regression guard.

♻️ Suggested tightening
-    // Verify the retry loop exists (while + re-prompt pattern)
-    assert.match(source, /while\s*\(true\)/);
-    assert.match(source, /Please try again/);
-    // Non-interactive still exits
-    assert.match(source, /isNonInteractive\(\)\)[\s\S]*?process\.exit\(1\)/);
+    const sandboxNameFlow = source.match(
+      /(?:async\s+)?function\s+promptValidatedSandboxName\s*\([^)]*\)\s*\{[\s\S]*?\n\}/
+    )?.[0];
+    assert.ok(sandboxNameFlow, "expected promptValidatedSandboxName implementation");
+    assert.match(sandboxNameFlow, /while\s*\(true\)/);
+    assert.match(sandboxNameFlow, /Please try again/);
+    assert.match(
+      sandboxNameFlow,
+      /if\s*\(isNonInteractive\(\)\)\s*\{[\s\S]*?process\.exit\(1\)/
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/onboard.test.js` around lines 1583 - 1588, The three loose regex
assertions operate on the whole file; restrict them to the "sandbox-name"
function block by first extracting that block from source (e.g. locate the
function or block labeled "sandbox-name" via a regex that captures its body into
a substring) and then run the three assert.match checks against that substring
instead of source; update the assertions that reference source to reference the
extracted block and keep the same patterns (/while\s*\(true\)/, /Please try
again/, /isNonInteractive\(\)\)[\s\S]*?process\.exit\(1\)/) so they only
validate the retry-loop and non-interactive exit inside the sandbox-name
function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/onboard.test.js`:
- Around line 1583-1588: The three loose regex assertions operate on the whole
file; restrict them to the "sandbox-name" function block by first extracting
that block from source (e.g. locate the function or block labeled "sandbox-name"
via a regex that captures its body into a substring) and then run the three
assert.match checks against that substring instead of source; update the
assertions that reference source to reference the extracted block and keep the
same patterns (/while\s*\(true\)/, /Please try again/,
/isNonInteractive\(\)\)[\s\S]*?process\.exit\(1\)/) so they only validate the
retry-loop and non-interactive exit inside the sandbox-name function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f30feffd-da47-4255-89d6-1c963d4fc28e

📥 Commits

Reviewing files that changed from the base of the PR and between 77465e1 and 6129bd4.

📒 Files selected for processing (1)
  • test/onboard.test.js

Address Copilot + CodeRabbit feedback: extract the function body
first, then assert within it. Prevents false positives from
unrelated while(true) or process.exit(1) elsewhere in the file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Creating sandbox fails and loops if sandbox name starts with a number

2 participants