Skip to content

feat(cli): add 'nemoclaw credentials' command for resetting stored keys#1597

Merged
ericksoa merged 3 commits intoNVIDIA:mainfrom
latenighthackathon:feat/credentials-reset-command
Apr 9, 2026
Merged

feat(cli): add 'nemoclaw credentials' command for resetting stored keys#1597
ericksoa merged 3 commits intoNVIDIA:mainfrom
latenighthackathon:feat/credentials-reset-command

Conversation

@latenighthackathon
Copy link
Copy Markdown
Contributor

@latenighthackathon latenighthackathon commented Apr 8, 2026

Summary

Problem

When a user enters an invalid API key during onboarding, the bad key is saved immediately to ~/.nemoclaw/credentials.json. On subsequent nemoclaw onboard runs, ensureApiKey() and ensureNamedCredential() see the stored value and return early — the prompt never fires, validation fails again with the same error, and the user has no documented escape hatch other than hand-editing the JSON file.

The recovery flow inside the onboard wizard does work if the user picks retry after validation fails, but #1568 specifically asks for either an automatic re-prompt on stored-credential validation failure, or a CLI command to clear a stored credential. This PR implements the CLI command (Option B in the issue) since it has the smaller blast radius and is decoupled from the onboard wizard.

Behavior

nemoclaw credentials list                  # list stored keys (no values)
nemoclaw credentials reset <KEY>            # remove a stored credential (with confirm)
nemoclaw credentials reset <KEY> --yes      # skip confirmation

After reset, the next nemoclaw onboard run re-prompts for the key.

  • list prints only key names, never values.
  • reset confirms by default (y/N); --yes/-y skips for scripting.
  • The existence check uses listCredentialKeys() only — getCredential() was avoided because it falls back to process.env, which would let an env-only key pass the check even though there is nothing on disk to delete.
  • deleteCredential preserves the existing 0o600 file permissions.
  • Returns false (not an error) when the file or key doesn't exist.

Test plan

  • 11 credentials tests pass (vitest), including 3 new tests for deleteCredential and listCredentialKeys
  • New test asserts file mode 0o600 is preserved across deleteCredential calls
  • Manual end-to-end smoke test in a clean Linux container:
    • credentials help → prints usage
    • credentials list (empty) → "No stored credentials."
    • Save a key, credentials list → prints key name only, no value
    • credentials reset KEY --yes → removes the key, prints next-step hint
    • credentials reset MISSING --yes → exits 1 with clear error
    • credentials reset ENV_ONLY_KEY --yes (env var set, no disk entry) → exits 1, does not "delete" anything
  • npx prettier --check clean
  • npx eslint clean
  • Signed commit + DCO sign-off on all commits

Closes #1568

Signed-off-by: latenighthackathon latenighthackathon@users.noreply.github.com

Summary by CodeRabbit

  • New Features

    • Added nemoclaw credentials with list (show stored credential keys) and reset <KEY> (remove a stored credential, with optional confirmation).
    • CLI help updated to include the new credentials commands.
  • Tests

    • Added unit tests for credential deletion behavior, file permissions, and listing of stored credential keys.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

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

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: 90dbaed5-77e5-4647-a3e8-d416a34e4009

📥 Commits

Reviewing files that changed from the base of the PR and between 73d19d9 and 03bfeb9.

📒 Files selected for processing (1)
  • bin/nemoclaw.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/nemoclaw.js

📝 Walkthrough

Walkthrough

Adds a top-level nemoclaw credentials CLI command with list and reset subcommands, implements deleteCredential and listCredentialKeys in the credentials library, consolidates prompt usage via a top-level askPrompt import, and adds unit tests for deletion and listing.

Changes

Cohort / File(s) Summary
Credential Management Library
src/lib/credentials.ts
Added export function deleteCredential(key: string): boolean and export function listCredentialKeys(): string[]. Minor adjustment to promptSecret Ctrl-C handling.
CLI / Command Dispatch
bin/nemoclaw.js
Statically imported deleteCredential, listCredentialKeys, and prompt (aliased askPrompt). Added credentials command, implemented credentialsCommand(args) with list and `reset [--yes
Tests
test/credentials.test.js
Added tests for deleteCredential (successful deletion, file permissions preservation, idempotency, missing-file behavior) and listCredentialKeys (empty result and sorted key names).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as "nemoclaw CLI"
  participant Cred as "credentials module"
  participant FS as "filesystem"

  User->>CLI: run "nemoclaw credentials reset <KEY>"
  CLI->>Cred: call listCredentialKeys()
  Cred->>FS: read `credentials.json`
  FS-->>Cred: credentials data
  Cred-->>CLI: key list
  CLI->>User: prompt confirmation via askPrompt
  User-->>CLI: confirm / cancel
  alt confirmed
    CLI->>Cred: call deleteCredential(<KEY>)
    Cred->>FS: read `credentials.json`
    Cred->>FS: write updated `credentials.json`
    FS-->>Cred: write result
    Cred-->>CLI: deletion success
    CLI->>User: print success and exit 0
  else cancelled or failure
    CLI->>User: print error/message and exit 1
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibble bytes and hop through keys,
I list and clear with gentle ease.
A prompt, a skip, a tidy sweep,
Old secrets gone — the burrow’s neat. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change—adding a 'nemoclaw credentials' command for resetting stored keys—which aligns with the core implementation in bin/nemoclaw.js.
Linked Issues check ✅ Passed The PR fully addresses issue #1568 by implementing option (B): a CLI command to list and reset stored credentials, enabling users to correct invalid keys without manual file editing.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the credentials reset command and supporting functions; no unrelated modifications are present.

✏️ 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.

Actionable comments posted: 1

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

67-82: Test intent and assertions are slightly misaligned for file mode.

Line 67 says “leaves the file mode intact,” but the test never asserts file permissions. Add a fs.statSync(...).mode & 0o777 check before/after deletion to lock this guarantee in.

Proposed test assertion addition
   it("deleteCredential removes a stored key and leaves the file mode intact", async () => {
     const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-creds-"));
     const credentials = await importCredentialsModule(home);

     credentials.saveCredential("NVIDIA_API_KEY", "nvapi-bad-key");
     credentials.saveCredential("OTHER_KEY", "other-value");
+    const credsFile = path.join(home, ".nemoclaw", "credentials.json");
+    expect(fs.statSync(credsFile).mode & 0o777).toBe(0o600);
     expect(credentials.listCredentialKeys()).toEqual(["NVIDIA_API_KEY", "OTHER_KEY"]);

     expect(credentials.deleteCredential("NVIDIA_API_KEY")).toBe(true);
+    expect(fs.statSync(credsFile).mode & 0o777).toBe(0o600);
     expect(credentials.getCredential("NVIDIA_API_KEY")).toBe(null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/credentials.test.js` around lines 67 - 82, The test description claims
"leaves the file mode intact" but never checks file permissions; add assertions
using fs.statSync to capture the credential file's permission mode (e.g.,
fs.statSync(<credential file path>).mode & 0o777) before calling
credentials.deleteCredential("NVIDIA_API_KEY") and again after deletion and
assert they are equal; locate the credential file used by
importCredentialsModule/home in this test and place the two mode checks around
the deleteCredential call so the test verifies the file mode is unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/nemoclaw.js`:
- Around line 927-955: The validation for the "credentials reset" command
incorrectly uses getCredential(key) which can return values from process.env;
change the check to only consult the persisted keys via listCredentialKeys()
(i.e., replace the combined getCredential/listCredentialKeys check with a single
existence check using listCredentialKeys().includes(key)) so the flow only
proceeds when the key is actually stored on disk before prompting/deleting
(leave deleteCredential, askPrompt, args handling unchanged).

---

Nitpick comments:
In `@test/credentials.test.js`:
- Around line 67-82: The test description claims "leaves the file mode intact"
but never checks file permissions; add assertions using fs.statSync to capture
the credential file's permission mode (e.g., fs.statSync(<credential file
path>).mode & 0o777) before calling
credentials.deleteCredential("NVIDIA_API_KEY") and again after deletion and
assert they are equal; locate the credential file used by
importCredentialsModule/home in this test and place the two mode checks around
the deleteCredential call so the test verifies the file mode is 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: CHILL

Plan: Pro

Run ID: e2358e9c-31d6-4745-a8dd-d0bc7ea4ad70

📥 Commits

Reviewing files that changed from the base of the PR and between 4c38bfa and 592f645.

📒 Files selected for processing (3)
  • bin/lib/credentials.js
  • bin/nemoclaw.js
  • test/credentials.test.js

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@latenighthackathon latenighthackathon force-pushed the feat/credentials-reset-command branch from 901f014 to 795da52 Compare April 8, 2026 04:02
@dknos
Copy link
Copy Markdown
Contributor

dknos commented Apr 8, 2026

Fixes already applied. CodeRabbit's findings from earlier are resolved:\n- ✅ bin/nemoclaw.js line 920: now uses listCredentialKeys().includes(key) (not getCredential)\n- ✅ test/credentials.test.js lines 81-82: file mode assertions in place\n\nAll checks passing. Ready to merge.

@wscurran wscurran added Getting Started Use this label to identify setup, installation, or onboarding issues. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. labels Apr 8, 2026
@wscurran
Copy link
Copy Markdown
Contributor

wscurran commented Apr 8, 2026

✨ Thanks for submitting this pull request, which proposes a way to add a nemoclaw credentials command for managing stored API keys, allowing users to reset invalid credentials without manual file editing.


Possibly related open issues:

@cv cv added the v0.0.10 Release target label Apr 8, 2026
@cv cv self-assigned this Apr 8, 2026
Copy link
Copy Markdown
Contributor

@cv cv left a comment

Choose a reason for hiding this comment

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

LGTM — security review PASS.

  • Credential deletion uses atomic write (temp + rename) preserving 0o600 mode
  • Only key names exposed, never values
  • No path traversal risk — keys are JSON properties, not filesystem paths
  • Interactive confirmation before deletion, --yes is opt-in
  • Prototype pollution prevented with hasOwnProperty.call()
  • Env-only keys correctly excluded from list (uses disk-only listCredentialKeys)
  • Good test coverage

No concerns.

@cv
Copy link
Copy Markdown
Contributor

cv commented Apr 8, 2026

Several v0.0.10 PRs just merged, including changes to onboard.js. Could you rebase on main to retrigger CI? Thanks!

When a user enters an invalid API key during onboarding it gets saved
to ~/.nemoclaw/credentials.json. On subsequent 'nemoclaw onboard' runs,
ensureApiKey() and ensureNamedCredential() see the stored value and
skip the prompt entirely, leaving the user stuck reusing the same bad
key with no documented escape hatch other than hand-editing the JSON.

Add a 'credentials' global subcommand:

  nemoclaw credentials list           - list stored keys (no values)
  nemoclaw credentials reset <KEY>    - remove a stored credential
  nemoclaw credentials reset <KEY> --yes  - skip confirmation prompt

After 'reset', the next 'nemoclaw onboard' run re-prompts for the
key. Values are never printed by 'list' or by any error path. The
existence check uses listCredentialKeys() only — getCredential()
falls back to process.env, which would let an env-only key pass the
check even though there is nothing on disk to delete.

Adds three new tests covering deleteCredential and listCredentialKeys
(round-trip with file-mode assertions, missing-file, and listing).

Closes NVIDIA#1568

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
@latenighthackathon latenighthackathon force-pushed the feat/credentials-reset-command branch from 795da52 to 73d19d9 Compare April 8, 2026 22:06
@latenighthackathon
Copy link
Copy Markdown
Contributor Author

latenighthackathon commented Apr 8, 2026

@cv Thanks for the thorough security review! Rebased onto current main — clean linear history, CI should pick up a fresh run, cheers!

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.

♻️ Duplicate comments (1)
bin/nemoclaw.js (1)

916-930: ⚠️ Potential issue | 🟡 Minor

Handle option-like/malformed reset args before key lookup.

nemoclaw credentials reset --yes currently treats --yes as <KEY> and returns a misleading “No stored credential found”. Validate <KEY> as a positional argument (non-flag), and reject unknown extra flags for safer scripting behavior.

Suggested patch
   if (sub === "reset") {
     const key = args[1];
-    if (!key) {
+    if (!key || key.startsWith("-")) {
       console.error("  Usage: nemoclaw credentials reset <KEY> [--yes]");
       console.error("  Run 'nemoclaw credentials list' to see stored keys.");
       process.exit(1);
     }
+    const unknown = args.slice(2).filter((arg) => arg !== "--yes" && arg !== "-y");
+    if (unknown.length > 0) {
+      console.error(`  Unknown option(s): ${unknown.join(", ")}`);
+      console.error("  Usage: nemoclaw credentials reset <KEY> [--yes]");
+      process.exit(1);
+    }
     // Only consult the persisted credentials file — getCredential() falls back
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/nemoclaw.js` around lines 916 - 930, The reset command currently treats
flags like "--yes" as the positional key (args[1]) causing misleading "No stored
credential" errors; change the argument handling in the reset flow to first
parse/validate that args[1] is a non-flag positional key (i.e., does not start
with "-") and explicitly accept only the known flag(s) (--yes / -y) elsewhere
(e.g., via args.includes("--yes") or args.includes("-y")); if args[1] is missing
or is an option/flag, print the usage and exit, and if any unknown extra
args/flags are present, reject them with an error before calling
listCredentialKeys() so listCredentialKeys() and the subsequent existence check
operate only on a validated key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@bin/nemoclaw.js`:
- Around line 916-930: The reset command currently treats flags like "--yes" as
the positional key (args[1]) causing misleading "No stored credential" errors;
change the argument handling in the reset flow to first parse/validate that
args[1] is a non-flag positional key (i.e., does not start with "-") and
explicitly accept only the known flag(s) (--yes / -y) elsewhere (e.g., via
args.includes("--yes") or args.includes("-y")); if args[1] is missing or is an
option/flag, print the usage and exit, and if any unknown extra args/flags are
present, reject them with an error before calling listCredentialKeys() so
listCredentialKeys() and the subsequent existence check operate only on a
validated key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c83dbc64-70be-467b-be50-b3d7981b78fc

📥 Commits

Reviewing files that changed from the base of the PR and between 795da52 and 73d19d9.

📒 Files selected for processing (3)
  • bin/nemoclaw.js
  • src/lib/credentials.ts
  • test/credentials.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/credentials.ts
  • test/credentials.test.js

latenighthackathon and others added 2 commits April 8, 2026 19:13
'nemoclaw credentials reset --yes' (with no key) previously treated
'--yes' as <KEY> and reported 'No stored credential found for --yes'.
Validate that <KEY> is a non-flag positional, and reject any trailing
arguments other than --yes / -y so scripted use stays predictable.

Addresses CodeRabbit feedback on NVIDIA#1597.

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
@ericksoa ericksoa merged commit 9ebc2f9 into NVIDIA:main Apr 9, 2026
9 of 11 checks passed
miyoungc added a commit that referenced this pull request Apr 9, 2026
## Summary
- Document `nemoclaw credentials list` and `nemoclaw credentials reset`
commands in commands reference (#1597)
- Add `--dry-run` flag documentation for `policy-add` (#1276)
- Update policy presets table: remove `docker` (#1647), add `brave` and
`brew`, update HuggingFace endpoint (#1540)
- Document `NEMOCLAW_LOCAL_INFERENCE_TIMEOUT` env var for local
providers (#1620)
- Document `NEMOCLAW_PROXY_HOST`/`NEMOCLAW_PROXY_PORT` env vars (#1563)
- Add troubleshooting entries for Docker group permissions (#1614),
sandbox survival after gateway restart (#1587), and proxy configuration
- Regenerate `nemoclaw-user-*` skills from updated docs

## Test plan
- [x] `make docs` builds without warnings
- [x] All pre-commit and pre-push hooks pass
- [ ] Verify rendered pages in docs site preview

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added `nemoclaw credentials list` command to display stored credential
names
* Added `nemoclaw credentials reset <KEY>` command with `--yes` flag to
remove credentials
  * Added `--dry-run` flag for policy-add to preview endpoint changes
  * New policy presets: `brave` and `brew`
* New configuration options: `NEMOCLAW_LOCAL_INFERENCE_TIMEOUT`,
`NEMOCLAW_PROXY_HOST`, and `NEMOCLAW_PROXY_PORT`

* **Documentation**
* Expanded troubleshooting guides for Docker permissions, sandbox
connectivity, local inference timeouts, and proxy configuration

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@latenighthackathon latenighthackathon deleted the feat/credentials-reset-command branch April 9, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. Getting Started Use this label to identify setup, installation, or onboarding issues. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). v0.0.10 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NemoClaw][All Platform]Stored invalid credential cannot be corrected through nemoclaw onboard — onboard skips key prompt when credential exists

5 participants