refactor(onboard): defer credential persistence until the review gate confirms#2226
refactor(onboard): defer credential persistence until the review gate confirms#2226latenighthackathon wants to merge 2 commits into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds deferred credential persistence to onboarding: credentials can be staged in-memory (via env vars) instead of written to Changes
Sequence DiagramsequenceDiagram
actor User
participant Onboard as Onboard Flow
participant Deferral as Deferral Manager
participant Storage as Storage Layer
User->>Onboard: Start interactive onboarding / provider selection
Onboard->>Deferral: beginDeferredCredentialPersistence()
activate Deferral
loop Credential entry / provider retries
User->>Onboard: Enter credential (or go Back)
Onboard->>Deferral: replaceNamedCredential(name, value)
Deferral->>Storage: set process.env (record name in pending set)
alt User navigates Back
Onboard->>Deferral: discardDeferredCredentials() / discardDeferredCredential(name)
Deferral->>Storage: clear staged env var
end
end
Onboard->>User: Show review/confirmation
alt User Confirms
User->>Onboard: Confirm
Onboard->>Deferral: flushDeferredCredentials()
Deferral->>Storage: persist pending entries to credentials.json (only if env still matches staged values)
Onboard->>Onboard: setupInference() (reads persisted values)
Onboard->>Deferral: endDeferredCredentialPersistence()
else User Declines / Aborts
User->>Onboard: Decline / Abort
Onboard->>Deferral: discardDeferredCredentials()
Deferral->>Deferral: clear pending set (no disk writes)
Onboard->>Deferral: endDeferredCredentialPersistence()
end
deactivate Deferral
Onboard->>User: Complete or Abort
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/onboard.ts`:
- Around line 779-783: The issue is that deferCredentialPersistence adds envName
to pendingDeferredCredentials and process.env immediately, but nothing removes
it when setupNim() returns to selectionLoop (so abandoned choices get flushed by
flushDeferredCredentials()). Fix by either moving the additions until the user
confirms the provider, or by removing staged creds when the user backs out:
ensure selectionLoop or the back/navigation path calls
pendingDeferredCredentials.delete(envName) and delete process.env[envName] for
the abandoned envName (or clear any previously staged envName before adding a
new one). Update the code paths around deferCredentialPersistence,
pendingDeferredCredentials, process.env[envName], setupNim(), and selectionLoop
so only the final confirmed credential remains for flushDeferredCredentials().
- Around line 3386-3388: The message always claims the API key is "stored in
~/.nemoclaw/credentials.json" even when credentials are still
deferred/in-memory; change the construction of apiKeyLine to reflect actual
persistence state by checking whether credentials have been flushed (use the
deferred-mode flag or call/inspect
flushDeferredCredentials()/deferredCredentials state) and render either "stored
in ~/.nemoclaw/credentials.json" when persisted or "stored in memory (will be
written after flush/confirmation)" when still deferred; update the apiKeyLine
assignment (referencing apiKeyLine, credentialEnv, provider, and
flushDeferredCredentials()) accordingly.
- Around line 6718-6725: The abort path currently always calls
discardDeferredCredentials() and prints “No credentials were persisted to disk,”
which is incorrect if an earlier iteration already called
flushDeferredCredentials(); fix by tracking whether any deferred credentials
were flushed (e.g., a boolean like deferredCredentialsFlushed set when
flushDeferredCredentials() runs) and change the abort logic to: if
deferredCredentialsFlushed is false, call discardDeferredCredentials() and print
the existing “No credentials were persisted” message; if true, discard only
remaining deferred writes (if needed) but print a safe message such as “Abort:
previously confirmed credentials may have been persisted; remaining deferred
credentials were discarded” to avoid the misleading guarantee. Ensure you
reference and update the logic around discardDeferredCredentials() and
flushDeferredCredentials() and the abort console.log outputs.
- Around line 6637-6640: The NVIDIA API key is being saved immediately because
setupNim() calls ensureApiKey() which uses saveCredential() directly, bypassing
the deferral you intended with deferInferenceCredentials and
beginDeferredCredentialPersistence(); modify the flow so ensureApiKey() (or its
caller in setupNim()) respects the deferral flag: either start
beginDeferredCredentialPersistence() earlier in setupNim() before ensureApiKey()
runs, or add a parameter to ensureApiKey() to avoid calling saveCredential()
directly and instead enqueue the credential with the deferred persistence
mechanism when deferInferenceCredentials is true; update references to
saveCredential(), ensureApiKey(), setupNim(), and
beginDeferredCredentialPersistence() accordingly.
🪄 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 Plus
Run ID: 7fac2c1b-6a42-40b5-aac0-771d47f01fd8
📒 Files selected for processing (2)
src/lib/onboard.tstest/onboard.test.ts
|
✨ Thanks for submitting this PR that proposes a way to refactor credential persistence — this could help provide a more secure configuration for credentials. Related open PRs: |
2230538 to
b20e3b3
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (3)
src/lib/onboard.ts (3)
6812-6827:⚠️ Potential issue | 🟡 MinorAbort message can overstate guarantees after prior flush.
If an earlier loop iteration already flushed deferred credentials, this path still prints “No credentials were persisted to disk.” That statement can be false.
Proposed fix
- let forceProviderSelection = false; + let forceProviderSelection = false; + let deferredCredentialsFlushed = false; @@ if (answer === "n" || answer === "no") { @@ - console.log(" No credentials were persisted to disk."); + if (deferredCredentialsFlushed) { + console.log(" Remaining deferred credentials were discarded."); + console.log(" Note: previously confirmed credentials may already be on disk."); + } else { + console.log(" No credentials were persisted to disk."); + } process.exit(0); } } @@ if (deferInferenceCredentials) { flushDeferredCredentials(); + deferredCredentialsFlushed = true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 6812 - 6827, The abort path prints "No credentials were persisted to disk." even if a prior iteration already called flushDeferredCredentials(); introduce or check a flag that tracks whether deferred credentials have been flushed (e.g., deferredCredentialsFlushed) and only call the “No credentials were persisted to disk.” message when no flush has occurred, or change the messaging to reflect that credentials may have been persisted earlier; update the logic around discardDeferredCredentials(), flushDeferredCredentials(), and deferInferenceCredentials to consult this flag so the printed statement accurately reflects whether any flush happened.
6726-6734:⚠️ Potential issue | 🟠 MajorDeferral is armed, but NVIDIA key entry still bypasses it.
Deferral is enabled here, but Line 4376 still uses
ensureApiKey()in the NVIDIA path, which is outside this deferred write path. That leaves a pre-gate persistence path for NVIDIA credentials.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 6726 - 6734, The NVIDIA credential branch still calls ensureApiKey(), causing immediate persistence outside the deferred flow started by deferInferenceCredentials/beginDeferredCredentialPersistence; update the NVIDIA path to use the deferred credential flow instead of ensureApiKey(): either call the credential collection function that queues writes into the deferred store (or create/route through a new ensureDeferredApiKey helper) so writes go through the same deferred persistence mechanism controlled by deferInferenceCredentials and finalized at the review gate, and remove any direct calls that persist to ~/.nemoclaw/credentials.json from the NVIDIA branch.
757-761:⚠️ Potential issue | 🟠 MajorDeferred staging can persist abandoned provider credentials.
Line 760 adds every entered env key to
pendingDeferredCredentials, but abandoned selections are never evicted beforeflushDeferredCredentials(). This can write credentials that are no longer part of the final confirmed selection.Proposed fix
if (deferCredentialPersistence) { + // Keep only the current in-progress inference credential staged. + // If the user backs out and chooses a different provider, do not + // retain abandoned staged credentials for a later flush. + for (const stagedEnvName of [...pendingDeferredCredentials]) { + if (stagedEnvName !== envName) { + pendingDeferredCredentials.delete(stagedEnvName); + delete process.env[stagedEnvName]; + } + } // Hold in memory + process.env; flushDeferredCredentials() will write // to ~/.nemoclaw/credentials.json after the review gate confirms. pendingDeferredCredentials.add(envName); process.env[envName] = key;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 757 - 761, pendingDeferredCredentials currently adds every envName when deferCredentialPersistence is true, which leaves abandoned selections in the set and causes flushDeferredCredentials() to persist stale credentials; fix by only adding envName to pendingDeferredCredentials when the provider/credential is actually confirmed (i.e., move the add(envName) into the confirmation/acceptance path) or ensure every abort/cancel/change flow removes it (call pendingDeferredCredentials.delete(envName) and delete process.env[envName]) so only confirmed envNames remain for flushDeferredCredentials; update the code paths around deferCredentialPersistence, pendingDeferredCredentials, envName and flushDeferredCredentials to implement one of these strategies.
🧹 Nitpick comments (1)
src/lib/onboard.ts (1)
734-736: Scrub staged env vars when discarding deferred credentials.
discardDeferredCredentials()disarms and clears the set, but leaves staged secret values inprocess.envfor the remainder of the process. Clearing those env vars is safer and keeps behavior robust if this function is reused without immediate process exit.Proposed fix
function discardDeferredCredentials() { + for (const envName of pendingDeferredCredentials) { + delete process.env[envName]; + } pendingDeferredCredentials.clear(); deferCredentialPersistence = false; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 734 - 736, discardDeferredCredentials currently clears pendingDeferredCredentials and flips deferCredentialPersistence but leaves any staged secret values in process.env; update discardDeferredCredentials to iterate the pendingDeferredCredentials set (e.g., for each key in pendingDeferredCredentials) and delete those entries from process.env before clearing the set and setting deferCredentialPersistence = false so staged env vars are scrubbed from the process when credentials are discarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 6812-6827: The abort path prints "No credentials were persisted to
disk." even if a prior iteration already called flushDeferredCredentials();
introduce or check a flag that tracks whether deferred credentials have been
flushed (e.g., deferredCredentialsFlushed) and only call the “No credentials
were persisted to disk.” message when no flush has occurred, or change the
messaging to reflect that credentials may have been persisted earlier; update
the logic around discardDeferredCredentials(), flushDeferredCredentials(), and
deferInferenceCredentials to consult this flag so the printed statement
accurately reflects whether any flush happened.
- Around line 6726-6734: The NVIDIA credential branch still calls
ensureApiKey(), causing immediate persistence outside the deferred flow started
by deferInferenceCredentials/beginDeferredCredentialPersistence; update the
NVIDIA path to use the deferred credential flow instead of ensureApiKey():
either call the credential collection function that queues writes into the
deferred store (or create/route through a new ensureDeferredApiKey helper) so
writes go through the same deferred persistence mechanism controlled by
deferInferenceCredentials and finalized at the review gate, and remove any
direct calls that persist to ~/.nemoclaw/credentials.json from the NVIDIA
branch.
- Around line 757-761: pendingDeferredCredentials currently adds every envName
when deferCredentialPersistence is true, which leaves abandoned selections in
the set and causes flushDeferredCredentials() to persist stale credentials; fix
by only adding envName to pendingDeferredCredentials when the
provider/credential is actually confirmed (i.e., move the add(envName) into the
confirmation/acceptance path) or ensure every abort/cancel/change flow removes
it (call pendingDeferredCredentials.delete(envName) and delete
process.env[envName]) so only confirmed envNames remain for
flushDeferredCredentials; update the code paths around
deferCredentialPersistence, pendingDeferredCredentials, envName and
flushDeferredCredentials to implement one of these strategies.
---
Nitpick comments:
In `@src/lib/onboard.ts`:
- Around line 734-736: discardDeferredCredentials currently clears
pendingDeferredCredentials and flips deferCredentialPersistence but leaves any
staged secret values in process.env; update discardDeferredCredentials to
iterate the pendingDeferredCredentials set (e.g., for each key in
pendingDeferredCredentials) and delete those entries from process.env before
clearing the set and setting deferCredentialPersistence = false so staged env
vars are scrubbed from the process when credentials are discarded.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 0dab8c86-4916-4d1f-a176-9ae95e9b9757
📒 Files selected for processing (1)
src/lib/onboard.ts
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/lib/onboard.ts (2)
6876-6883:⚠️ Potential issue | 🟠 MajorAbort can over-promise after a previously confirmed retry.
If an earlier loop iteration already hit
flushDeferredCredentials(), aborting here still says “No credentials were persisted to disk.” That becomes false as soon as any previous retry was confirmed and flushed.Also applies to: 6886-6891
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 6876 - 6883, The abort messaging incorrectly always claims “No credentials were persisted to disk.” even when a prior retry already called flushDeferredCredentials(); update the abort branches (the block that currently calls discardDeferredCredentials() and the similar block at 6886-6891) to check whether deferred credentials were already flushed and conditionally log the correct message: if flushDeferredCredentials() (or a boolean flag set when flushDeferredCredentials() runs) indicates credentials were written, log that credentials were persisted and provide guidance; otherwise call discardDeferredCredentials() and log that no credentials were persisted. Ensure you reference and use the existing functions flushDeferredCredentials() and discardDeferredCredentials() (or set/read a flushed flag where flushDeferredCredentials() runs) so the message accurately reflects state before calling process.exit(0).
715-727:⚠️ Potential issue | 🟠 MajorBacking out to another provider can still flush the abandoned key.
The env name is staged as soon as the key is entered, and
flushDeferredCredentials()later persists every staged name. I still don't see any"back"/"selection"path removing the abandoned env name orprocess.envvalue before the user picks a different provider, so confirming the second choice can write both credentials.Also applies to: 757-761
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 715 - 727, The current flushDeferredCredentials() unconditionally persists any env names in pendingDeferredCredentials, allowing an abandoned/previous provider's key to be saved; fix by tracking the staged credential value when staging (store a map stagedCredentialValues: Map<string,string> alongside pendingDeferredCredentials), then in flushDeferredCredentials() only call saveCredential(envName, value) if process.env[envName] === stagedCredentialValues.get(envName) and the value is non-empty; also ensure any “back”/“selection” or cancellation paths remove the envName from pendingDeferredCredentials and stagedCredentialValues (and clear process.env[envName] if appropriate) so abandoned entries cannot be flushed—refer to flushDeferredCredentials, pendingDeferredCredentials, saveCredential, endDeferredCredentialPersistence and the code that stages env names when a key is entered to implement this.
🧹 Nitpick comments (1)
src/lib/onboard.ts (1)
6795-6798: Pair the deferral lifecycle with afinally.If anything throws between
beginDeferredCredentialPersistence()andendDeferredCredentialPersistence(), the module-scoped flag/set stay armed for the rest of the process. Wrapping this retry loop in atry/finallywould avoid leaking deferred state into later prompts or tests.Also applies to: 6916-6918
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 6795 - 6798, The code arms deferred credential persistence via deferInferenceCredentials and calls beginDeferredCredentialPersistence() but does not guarantee endDeferredCredentialPersistence() runs on error; wrap the block that runs while deferInferenceCredentials is true (the retry loop and any logic between beginDeferredCredentialPersistence() and endDeferredCredentialPersistence()) in a try/finally so that endDeferredCredentialPersistence() is always called in the finally clause (also apply the same change to the other occurrence around lines 6916-6918); reference deferInferenceCredentials, beginDeferredCredentialPersistence, and endDeferredCredentialPersistence to locate and protect the lifecycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/onboard.ts`:
- Around line 766-770: The success message is being printed twice because
saveCredential already logs the "~/.nemoclaw/credentials.json" success line;
remove the duplicate prints in the immediate flow by deleting the three
console.log calls after saveCredential (the two blank console.log("") calls and
the console.log(` Key saved to ~/.nemoclaw/credentials.json (mode 600)`)) in
the block that sets process.env[envName] so only the credential-saving function
emits the message.
- Around line 757-764: Deferred-mode keys are only written to process.env and
pendingDeferredCredentials but selection-time validators still call
getCredential(...) which reads persisted storage, causing new keys to be
invisible; update the credential lookup so staged keys are visible: change
getCredential (the central credential getter used by the selection-time and
remote model validators) to first check process.env[envName] and/or
pendingDeferredCredentials (the set used when deferCredentialPersistence is
true) and return that staged value if present before falling back to persisted
credentials; this ensures validators at selection time and in the remote model
validators see the freshly entered key without waiting for
flushDeferredCredentials().
---
Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 6876-6883: The abort messaging incorrectly always claims “No
credentials were persisted to disk.” even when a prior retry already called
flushDeferredCredentials(); update the abort branches (the block that currently
calls discardDeferredCredentials() and the similar block at 6886-6891) to check
whether deferred credentials were already flushed and conditionally log the
correct message: if flushDeferredCredentials() (or a boolean flag set when
flushDeferredCredentials() runs) indicates credentials were written, log that
credentials were persisted and provide guidance; otherwise call
discardDeferredCredentials() and log that no credentials were persisted. Ensure
you reference and use the existing functions flushDeferredCredentials() and
discardDeferredCredentials() (or set/read a flushed flag where
flushDeferredCredentials() runs) so the message accurately reflects state before
calling process.exit(0).
- Around line 715-727: The current flushDeferredCredentials() unconditionally
persists any env names in pendingDeferredCredentials, allowing an
abandoned/previous provider's key to be saved; fix by tracking the staged
credential value when staging (store a map stagedCredentialValues:
Map<string,string> alongside pendingDeferredCredentials), then in
flushDeferredCredentials() only call saveCredential(envName, value) if
process.env[envName] === stagedCredentialValues.get(envName) and the value is
non-empty; also ensure any “back”/“selection” or cancellation paths remove the
envName from pendingDeferredCredentials and stagedCredentialValues (and clear
process.env[envName] if appropriate) so abandoned entries cannot be
flushed—refer to flushDeferredCredentials, pendingDeferredCredentials,
saveCredential, endDeferredCredentialPersistence and the code that stages env
names when a key is entered to implement this.
---
Nitpick comments:
In `@src/lib/onboard.ts`:
- Around line 6795-6798: The code arms deferred credential persistence via
deferInferenceCredentials and calls beginDeferredCredentialPersistence() but
does not guarantee endDeferredCredentialPersistence() runs on error; wrap the
block that runs while deferInferenceCredentials is true (the retry loop and any
logic between beginDeferredCredentialPersistence() and
endDeferredCredentialPersistence()) in a try/finally so that
endDeferredCredentialPersistence() is always called in the finally clause (also
apply the same change to the other occurrence around lines 6916-6918); reference
deferInferenceCredentials, beginDeferredCredentialPersistence, and
endDeferredCredentialPersistence to locate and protect the lifecycle.
🪄 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: Enterprise
Run ID: 4e8b9770-fb25-41cd-b209-b90d50bf93f2
📒 Files selected for processing (1)
src/lib/onboard.ts
408d724 to
1104aed
Compare
|
Rebased onto |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/lib/onboard.ts (1)
791-795:⚠️ Potential issue | 🟡 MinorAvoid double-printing the save banner in the immediate path.
If
saveCredential()still owns the interactivecredentials.jsonsuccess output, these lines will print the same status twice. Please verify the helper and keep the message in one place only.#!/bin/bash set -euo pipefail echo "== saveCredential definition / output ==" rg -n -C4 '\bsaveCredential\s*\(|credentials\.json' src/lib/credentials.ts echo echo "== immediate-save branch in onboard.ts ==" sed -n '788,796p' src/lib/onboard.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 791 - 795, The code prints the credentials.json success banner twice: once inside saveCredential(...) and again immediately after in the onboard immediate-save branch (the console.log block that references ~/.nemoclaw/credentials.json); inspect the saveCredential implementation in credentials.ts to see if it already logs the success message, then remove the duplicate output by either deleting the console.log(...) lines here (the immediate-save block that uses envName and key) or suppressing logging inside saveCredential so only one place logs the success—ensure only one of the two (saveCredential or the onboard immediate-save branch) emits the credentials.json save banner.
🧹 Nitpick comments (1)
src/lib/onboard.ts (1)
6861-7007: Extract the deferred review-gate orchestration out ofonboard().This patch adds more stateful branching to an already complexity-suppressed retry loop. Pulling the defer/flush/discard/summary flow into a dedicated helper would make this path much easier to reason about and test.
As per coding guidelines,
TypeScript files must maintain cyclomatic complexity limit of 20, ratcheting down to 15. Enforced by ESLint.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 6861 - 7007, The review gate and deferred-credential orchestration (calculation of deferInferenceCredentials, beginDeferredCredentialPersistence(), the summary/confirmation prompt block that calls promptValidatedSandboxName(), formatOnboardConfigSummary(), promptOrDefault(), discardDeferredCredentials(), haveDeferredCredentialsBeenFlushed(), flushDeferredCredentials(), and endDeferredCredentialPersistence()) should be extracted from onboard() into a single helper (e.g., handleDeferredCredentialReviewGate or confirmInferenceCredentials) that accepts the mutable pieces it needs (provider, model, credentialEnv, webSearchConfig, selectedMessagingChannels, sandboxName, dangerouslySkipPermissions, isNonInteractive, etc.) and returns the updated sandboxName plus a flag indicating whether to proceed or abort; replace the inline block with a call to this helper, ensure beginDeferredCredentialPersistence()/endDeferredCredentialPersistence() are paired inside the helper, preserve the priorFlush logic and process.exit behavior by returning an abort signal instead of exiting deep in onboarding, and add unit tests for the new helper to reduce onboard() cyclomatic complexity to satisfy ESLint limits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 791-795: The code prints the credentials.json success banner
twice: once inside saveCredential(...) and again immediately after in the
onboard immediate-save branch (the console.log block that references
~/.nemoclaw/credentials.json); inspect the saveCredential implementation in
credentials.ts to see if it already logs the success message, then remove the
duplicate output by either deleting the console.log(...) lines here (the
immediate-save block that uses envName and key) or suppressing logging inside
saveCredential so only one place logs the success—ensure only one of the two
(saveCredential or the onboard immediate-save branch) emits the credentials.json
save banner.
---
Nitpick comments:
In `@src/lib/onboard.ts`:
- Around line 6861-7007: The review gate and deferred-credential orchestration
(calculation of deferInferenceCredentials, beginDeferredCredentialPersistence(),
the summary/confirmation prompt block that calls promptValidatedSandboxName(),
formatOnboardConfigSummary(), promptOrDefault(), discardDeferredCredentials(),
haveDeferredCredentialsBeenFlushed(), flushDeferredCredentials(), and
endDeferredCredentialPersistence()) should be extracted from onboard() into a
single helper (e.g., handleDeferredCredentialReviewGate or
confirmInferenceCredentials) that accepts the mutable pieces it needs (provider,
model, credentialEnv, webSearchConfig, selectedMessagingChannels, sandboxName,
dangerouslySkipPermissions, isNonInteractive, etc.) and returns the updated
sandboxName plus a flag indicating whether to proceed or abort; replace the
inline block with a call to this helper, ensure
beginDeferredCredentialPersistence()/endDeferredCredentialPersistence() are
paired inside the helper, preserve the priorFlush logic and process.exit
behavior by returning an abort signal instead of exiting deep in onboarding, and
add unit tests for the new helper to reduce onboard() cyclomatic complexity to
satisfy ESLint limits.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: a95e3806-044a-4a2f-b1d2-64fa1a7adbc3
📒 Files selected for processing (1)
src/lib/onboard.ts
Addresses CodeRabbit concerns from the 04-24 review on NVIDIA#2226: - `pendingDeferredCredentials` is now `Map<string, string>` (envName → staged value). `flushDeferredCredentials()` only persists entries where `process.env[envName]` still equals the staged value, so a back-nav path that abandoned a provider's key cannot flush it alongside the final selection. Defense-in-depth with the existing `discardStagedCredential` path in `setupNim`'s `selectionLoop`. - Wrap the inference retry loop in `try/finally` so `endDeferredCredentialPersistence()` always disarms the module-scoped deferral flag, even if the loop throws. Prevents leaking state into later prompts or tests. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
05a3645 to
cd1aa2a
Compare
|
Addressed CodeRabbit nits - Cheers! |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/lib/onboard.ts (1)
720-745:⚠️ Potential issue | 🔴 CriticalStaged keys still need to be visible to every later lookup path.
These helpers now rely on
process.env/pendingDeferredCredentials, but this file still re-reads credentials viagetCredential(...)during selection validation and viahydrateCredentialEnv()at the top ofsetupInference(). Ifsrc/lib/credentials.ts#getCredentialis still reading persisted storage, a freshly entered key stays invisible before confirm, and a post-confirm "retry credential" insidesetupInference()will fall back to the old saved key on the next loop. Either make the shared lookup prefer staged env values or stop deferring once the first review gate has been accepted.#!/bin/bash set -euo pipefail echo "=== credential lookup implementation ===" rg -n -C4 '\bfunction getCredential\b|\bfunction saveCredential\b|process\.env|credentials\.json' src/lib/credentials.ts echo echo "=== onboard call sites that re-read credentials after staging ===" rg -n -C2 '\bgetCredential\(|\bhydrateCredentialEnv\(' src/lib/onboard.tsExpected result:
getCredential()(or a shared helper used by these call sites) should prefer stagedprocess.envvalues before persisted storage while deferral is active.Also applies to: 795-803
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/onboard.ts` around lines 720 - 745, The staged credential values aren't being honored by later lookups in setupInference and selection validation; update the shared lookup logic so getCredential (or the helper used by hydrateCredentialEnv and setupInference) prefers staged values when deferCredentialPersistence is active by first checking process.env[envName] and pendingDeferredCredentials for a non-empty stagedValue before falling back to persisted storage, or alternatively clear deferCredentialPersistence (call endDeferredCredentialPersistence()) immediately after the first review/confirm gate is accepted so subsequent getCredential/hydrateCredentialEnv calls see the freshly staged value; touch the functions getCredential, hydrateCredentialEnv, setupInference, flushDeferredCredentials, and endDeferredCredentialPersistence to implement this consistent precedence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/lib/onboard.ts`:
- Around line 720-745: The staged credential values aren't being honored by
later lookups in setupInference and selection validation; update the shared
lookup logic so getCredential (or the helper used by hydrateCredentialEnv and
setupInference) prefers staged values when deferCredentialPersistence is active
by first checking process.env[envName] and pendingDeferredCredentials for a
non-empty stagedValue before falling back to persisted storage, or alternatively
clear deferCredentialPersistence (call endDeferredCredentialPersistence())
immediately after the first review/confirm gate is accepted so subsequent
getCredential/hydrateCredentialEnv calls see the freshly staged value; touch the
functions getCredential, hydrateCredentialEnv, setupInference,
flushDeferredCredentials, and endDeferredCredentialPersistence to implement this
consistent precedence.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 19323d52-148c-40bf-a3d1-aea88d645aea
📒 Files selected for processing (1)
src/lib/onboard.ts
Addresses CodeRabbit concerns from the 04-24 review on NVIDIA#2226: - `pendingDeferredCredentials` is now `Map<string, string>` (envName → staged value). `flushDeferredCredentials()` only persists entries where `process.env[envName]` still equals the staged value, so a back-nav path that abandoned a provider's key cannot flush it alongside the final selection. Defense-in-depth with the existing `discardStagedCredential` path in `setupNim`'s `selectionLoop`. - Wrap the inference retry loop in `try/finally` so `endDeferredCredentialPersistence()` always disarms the module-scoped deferral flag, even if the loop throws. Prevents leaking state into later prompts or tests. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
cd1aa2a to
141418f
Compare
Addresses CodeRabbit concerns from the 04-24 review on NVIDIA#2226: - `pendingDeferredCredentials` is now `Map<string, string>` (envName → staged value). `flushDeferredCredentials()` only persists entries where `process.env[envName]` still equals the staged value, so a back-nav path that abandoned a provider's key cannot flush it alongside the final selection. Defense-in-depth with the existing `discardStagedCredential` path in `setupNim`'s `selectionLoop`. - Wrap the inference retry loop in `try/finally` so `endDeferredCredentialPersistence()` always disarms the module-scoped deferral flag, even if the loop throws. Prevents leaking state into later prompts or tests. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
141418f to
a7806b0
Compare
… confirms Follow-up to NVIDIA#2221 addressing the remaining credential-persistence window CodeRabbit flagged: setupNim's credential prompt writes to ~/.nemoclaw/credentials.json via saveCredential as soon as the user types a key, so a later abort at the review gate still left that key on disk. Introduce a module-level defer flag so credential-prompt writes are held in memory until the gate confirms, plus follow-up fixes addressing CodeRabbit review on b20e3b3. Mechanism - beginDeferredCredentialPersistence() arms the flag, clears the pending set, resets the flush-tracking bit. Called once at the top of the inference retry loop in onboard(), only when the review gate will actually fire (interactive + not --dangerously-skip-permissions). - replaceNamedCredential(), when the flag is armed, sets process.env and adds the env name to pendingDeferredCredentials, skipping the saveCredential call. Prints "Key held in memory; will be saved to credentials.json on confirm." instead of the usual "Key saved". - flushDeferredCredentials() iterates the pending set, calls saveCredential(envName, process.env[envName]) for each, clears the set, and marks deferredCredentialsFlushed so a subsequent abort after a confirmed-then-failed retry iteration tells the truth. - discardDeferredCredentials() now also clears the pending env vars from process.env, not just the set. Called on gate abort. - endDeferredCredentialPersistence() clears, disarms, resets the flush bit. Called after the retry loop breaks successfully so downstream prompts (Brave, messaging tokens) use the normal immediate path. - discardStagedCredential(envName) removes a single envName from the pending set and process.env. Used when setupNim goes back to selectionLoop after a credential was staged for an abandoned provider. setupNim changes (close CodeRabbit Majors at lines 761 and 6798) - In deferred mode only, route the NVIDIA API key prompt through replaceNamedCredential (respects the flag) instead of ensureApiKey (which writes immediately). Non-deferred mode still calls ensureApiKey so existing mocks and single-path flows are unchanged. The onboarding box UI is preserved inline; validateNvidiaApiKeyValue runs the same starts-with-nvapi- check ensureApiKey did. - Track stagedCredentialEnvThisIteration across selectionLoop iterations and discardStagedCredential() at the top of each new iteration so a user who enters key A for provider X, navigates back, then picks provider Y with key B only has B in pendingDeferredCredentials when the gate flushes. formatOnboardConfigSummary changes (close CodeRabbit Minor at line 3378) - New credentialStorageState parameter distinguishes "deferred" (key held in memory) from "persisted" (already on disk). The summary now says "held in memory — will be saved to ~/.nemoclaw/credentials.json on confirm" when the key is still pending so the gate's promise matches reality. The call site passes the state based on pendingDeferredCredentials membership. Abort flow (closes CodeRabbit Minor at line 6883) - The gate's n/no branch now checks haveDeferredCredentialsBeenFlushed() before printing its after-message. If no flush ever happened it says "No credentials were persisted to disk." as before. If an earlier retry iteration flushed and this iteration aborted, it acknowledges that "credentials confirmed in an earlier attempt are already in credentials.json" and points to nemoclaw credentials reset <KEY>. Non-interactive and --dangerously-skip-permissions flows skip the deferral entirely and use the existing immediate-persist path. Scope Touches only onboard.ts. The other saveCredential call sites (configureWebSearch, setupMessagingChannels, sandbox-channels, sandbox-config) already run after the gate and dont need deferral. Tests - test/onboard.test.ts — 135 tests pass. - test/onboard-selection.test.ts — 33 tests pass (verifies setupNim back-nav + NVIDIA Endpoints path). - Typecheck clean. Stacks on NVIDIA#2221 — depends on the gate and formatOnboardConfigSummary landing first. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Addresses CodeRabbit concerns from the 04-24 review on NVIDIA#2226: - `pendingDeferredCredentials` is now `Map<string, string>` (envName → staged value). `flushDeferredCredentials()` only persists entries where `process.env[envName]` still equals the staged value, so a back-nav path that abandoned a provider's key cannot flush it alongside the final selection. Defense-in-depth with the existing `discardStagedCredential` path in `setupNim`'s `selectionLoop`. - Wrap the inference retry loop in `try/finally` so `endDeferredCredentialPersistence()` always disarms the module-scoped deferral flag, even if the loop throws. Prevents leaking state into later prompts or tests. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
a7806b0 to
93d15db
Compare
|
Closing — #2554 (host-side credential hardening) made |
Summary
Follow-up to #2221 addressing the remaining credential-persistence window CodeRabbit flagged.
`setupNim`'s credential prompt writes the API key to `~/.nemoclaw/credentials.json` via `saveCredential` as soon as the user types a valid value. An abort at the review gate further down still left that key on disk — the existing abort message worked around it by directing users to `nemoclaw credentials reset `.
This PR introduces a module-level defer flag so credential-prompt writes are held in memory (`process.env` only) and only flushed to disk after the review gate confirms. A clean abort now leaves nothing on disk.
Stacks on #2221
This PR depends on #2221 landing first — it wires the new `flushDeferredCredentials` / `discardDeferredCredentials` calls into the review gate that #2221 adds.
Mechanism
Abort flow
The gate's `n/no` branch now:
Replacing the earlier `credentials reset` workaround copy.
Non-interactive and --dangerously-skip-permissions
Skip the deferral entirely (no gate to flush at). They use the existing immediate-persist path unchanged.
Scope
Touches only `src/lib/onboard.ts` (+90 / −11). The other `saveCredential` call sites (`configureWebSearch`, `setupMessagingChannels`, `sandbox-channels`, `sandbox-config`) already run after the gate and don't need deferral.
Testing
The defer helpers themselves are module-scoped with no test seams that don't require stdin simulation; their correctness is covered by the existing onboard integration tests which exercise both the interactive (deferral on) and non-interactive (deferral off) code paths.
Checklist
Signed-off-by: latenighthackathon latenighthackathon@users.noreply.github.com
Summary by CodeRabbit
Enhancements
Bug Fixes