Skip to content

Stop the Keychain prompt storm and add an OS keyring opt-out (Fixes #2928) - #3073

Merged
acoliver merged 6 commits into
mainfrom
issue2928
Aug 6, 2026
Merged

Stop the Keychain prompt storm and add an OS keyring opt-out (Fixes #2928)#3073
acoliver merged 6 commits into
mainfrom
issue2928

Conversation

@acoliver

@acoliver acoliver commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

TLDR

SecureStore could not tell "this credential does not exist" from "macOS refused / the user cancelled". Every failure collapsed into "not found", so LLxprt's existing classifyError never fired, users saw a misleading "not authenticated" state, and nothing could stop it from re-prompting on every subsequent read.

This delivers the two things that actually stop the storm, plus a way out:

  1. Error fidelity where the native binding surfaces an error at all — genuine user cancellation now classifies as DENIED instead of a silently degradable UNAVAILABLE.
  2. One process-wide state transition, enforced at the single real chokepoint. The first DENIED/LOCKED latches the OS keyring unusable for the process, emits exactly one warning, and guarantees zero further native Keychain entry.
  3. An explicit opt-outLLXPRT_DISABLE_OS_KEYRING=1 and a security.disableOsKeyring setting, honored before @napi-rs/keyring is even imported.

Reviewers please look at: the placement of the latch in createGuardedAdapter() (that choice is the crux of the PR), the deliberately narrow cancellation matching, and the machine-secret write-path guard that refuses to mint a replacement root of trust.

Dive Deeper

Why the read path is not fixed here (and what is)

I read the source of the binding rather than guessing. In Brooooooklyn/keyring-node main:

  • src/async_entry.rs, PasswordTask::compute -> Ok(self.inner.get_password().ok()) — the .ok() discards the OSStatus.
  • src/async_entry.rs, EntryTask delete -> Ok(Some(self.inner.delete_credential().is_ok())) — same collapse.
  • src/entry.rs (sync Entry) — identical.
  • set_password / set_secret DO propagate via map_err(anyhow::Error::from)?.

Confirmed empirically on darwin against the installed binding: getPassword() on a missing entry returns null, deleteCredential() returns false.

@napi-rs/keyring@1.3.0 is installed and is the latest published version, so upgrading cannot fix the read path. findCredentials is not a usable disambiguator either — on macOS its filter_map reads get_generic_password for every account under the service and drops failures silently, so using it would multiply prompts, which is the exact thing this issue exists to stop.

Recovering read-path OSStatus therefore requires forking/vendoring/replacing a Rust native module (toolchain plus prebuilt binaries for darwin-arm64/x64, linux-x64/arm64 gnu+musl, win32-x64/arm64). That is a dependency + build + CI change and is deliberately out of scope, tracked in #3067. It plugs in behind the same factory with no call-site changes when it lands.

The write, delete-verification and probe paths do propagate errors today, which is what the latch runs on.

The latch is at the adapter boundary, not in SecureStore

The first cut put the latch in SecureStore's catch sites. That was wrong: machine-secret.ts (readFromKeyring, persistToKeyringLocked) and MCP keychain-token-storage.ts hold adapters directly and catch their own errors, and SecureStore's own list() and write-verification swallow errors before classification. All of those bypassed it.

It now lives in createGuardedAdapter() in default-keyring-adapter.ts — the one place every consumer's adapter comes from. Each wrapped method checks the session before entering native code and routes any thrown native error through the shared noteKeyringError before rethrowing it unchanged. So the latch fires even when the immediate caller swallows the error, and a consumer holding an adapter cached from before the latch gets a typed UNAVAILABLE instead of a prompt.

classifyError moved to a new dependency-leaf module (classify-error.ts) purely to let the adapter use it without an import cycle back into secure-store.ts.

Classification is deliberately narrow

A process-wide latch is close to irreversible within a session, so the trigger has to be precise:

  • Cancellation matches only errsecusercanceled / errseccanceled and word-boundary user cancel(l)ed / cancel(l)ed by the user. A bare cancel substring would also match "request cancelled due to timeout", and the binding accepts an AbortSignal on every method.
  • Node syscall/errno errors (EACCES, EPERM, …) never latch. Their messages read as "permission denied" without the keyring having refused anything.
  • TIMEOUT and UNAVAILABLE do not latch. RUNTIME_REPLACED stays terminal, does not latch, and is never converted into the session error.

Machine-secret durability

Two guards, both about not orphaning credentials:

  • While the keyring is disabled, the fallback write path resolves the machine secret read-only and refuses to mint a replacement if any v:2 envelope exists. A keychain-resident secret may be present but unreachable; replacing it would permanently orphan every envelope sealed under it, and the new envelopes would break on the next healthy start. Minting is still allowed when there is nothing to orphan, so a first-time opt-out user gets a normal v:2 file-backed store rather than a silent v:1 downgrade.
  • The v:2 read path fails closed with an actionable error instead of generating a new root of trust.

An earlier draft mirrored the machine secret to disk on every v:2 write to make mode-switching seamless. That was removed: it put the keychain-resident root of trust on disk, was fail-open on persistence failure, could install a mismatched/stale secret, and raced other writers. The reasoning is recorded in project-plans/issue2928/PLAN.md.

Notes on collateral changes

  • packages/storage/test-setup-storage-isolation.ts is loaded by both bunfig.toml and vitest.config.ts, so the Bun-only reset hooks live in a separate test-setup-bun-session-reset.ts. Importing bun:test in the shared file breaks Vitest collection outright.
  • Three pre-existing tests injected a locked error to simulate a transient failure and then asserted recovery. LOCKED now intentionally latches, so those were switched to non-latching TIMEOUT/UNAVAILABLE messages, preserving each test's original intent.

Reviewer Test Plan

The opt-out (no Keychain access at all):

LLXPRT_DISABLE_OS_KEYRING=1 llxprt

Then confirm no llxprt-code-* items are touched. On macOS, watch with Console.app filtered on securityd, or simply confirm you get no prompts and that credentials round-trip through ~/.llxprt/secure-store/. Equivalent via settings: set security.disableOsKeyring: true and restart.

The prompt storm itself (the real-world reproduction, on macOS with a keychain entry whose ACL will prompt): start LLxprt and press Cancel/Escape on the first Keychain dialog during a credential write. Before this change you would keep getting dialogs; now you should get exactly one stderr warning naming the cause and remedy, and then silence for the rest of the session, with credentials served from the encrypted fallback.

Automated:

cd packages/storage && npm test          # bun suites, includes the 25 new cases
cd packages/storage && npx vitest run    # confirms the shared setup still collects

The new suite is packages/storage/test-bun/secure-store.keyring-session.bun.ts. Worth spot-checking that the tests fail if you delete the logic: the post-latch cases use fresh counting adapters and assert zero adapter calls, so they cannot pass merely by hitting the same denied adapter again.

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Verified locally on macOS: npm run typecheck, npm run lint, npm run lint:eslint-guard, npm run format, npm run build all exit 0; packages/storage bun and vitest suites both green; smoke test via bun scripts/start.ts --profile-load stepfun-37 returns normally.

npm run test at the repo root has pre-existing failures on this machine that are unrelated and reproduce identically with the branch stashed: 9 image tests fail because sharp is not installed in the project and resolves to a stale v0.33.5 in $HOME that lacks metadata.autoOrient, and the agents package produces rotating 30s timeouts (a different set every run, present at baseline too). Relying on CI for the authoritative signal.

Linked issues / bugs

Fixes #2928

Read-path OSStatus fidelity is explicitly deferred and tracked in #3067. Companion to the replaced-runtime fast-fail work in #2926.

Summary by CodeRabbit

  • New Features

    • Added a setting to disable OS keyring/keychain storage and use encrypted file-based credential storage instead.
    • The option can also be enabled with LLXPRT_DISABLE_OS_KEYRING=1 and takes effect after restarting.
  • Bug Fixes

    • Improved handling of unavailable, locked, denied, and transient keyring errors.
    • Prevented credential loss when secure storage is unavailable or fallback data requires protection.
  • Documentation

    • Documented the new security setting, defaults, restart requirement, and environment variable.

…2928)

SecureStore could not tell "this credential does not exist" from "macOS
refused / the user cancelled", so a denied Keychain prompt looked like a
missing credential, the existing classifier never fired, and every
subsequent read re-prompted with nothing able to stop it.

Three changes, all in TypeScript:

Error fidelity. classifyError moves to a new dependency-leaf module
(classify-error.ts) so the adapter boundary can use it without an import
cycle, and it now recognises genuine user cancellation as DENIED instead
of letting it fall through to a silently degradable UNAVAILABLE. The
match is deliberately narrow -- errSecUserCanceled / errSecCanceled and
word-boundary "user cancel(l)ed" -- because @napi-rs/keyring accepts an
AbortSignal on every method and abort/timeout text must not latch.
Syscall/errno errors (EACCES, EPERM) never latch either: their messages
read as "permission denied" without the keyring having refused anything.

One session-level transition. keyring-session-state.ts holds the
process-wide latch. It is enforced at the single real chokepoint --
createGuardedAdapter() in default-keyring-adapter.ts -- through which
SecureStore, machine-secret and MCP token storage all obtain their
adapter. The guard checks the session before each native call and routes
every thrown native error through the shared noteKeyringError before
rethrowing it unchanged, so the first DENIED/LOCKED latches the keyring
off, emits exactly one stderr warning, and yields zero further native
entry -- even for an adapter a consumer cached before the latch, and even
when that consumer swallows the error. RUNTIME_REPLACED stays terminal
and is never absorbed.

An explicit opt-out. LLXPRT_DISABLE_OS_KEYRING=1 and the
security.disableOsKeyring setting make the factory return null before
@napi-rs/keyring is imported, so zero Keychain operations occur including
for llxprt-code-machine-secret. The setting is pushed into storage at the
top of finalizeConfig, ahead of profile auth wiring, because that path
performs a real SecureStore read.

While the keyring is disabled the fallback write path resolves the
machine secret read-only and refuses to mint a replacement when a v:2
envelope exists, since a keychain-resident secret may be present but
unreachable and replacing it would permanently orphan those envelopes.
Minting is still allowed when there is nothing to orphan. The v:2 read
path fails closed with an actionable error rather than generating a new
root of trust.

Read-path fidelity is deliberately not addressed here. @napi-rs/keyring
discards the OSStatus in PasswordTask::compute via Rust .ok(), and 1.3.0
is already the latest release, so recovering it requires changing the
native binding. Tracked in #3067.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 50481a96-89b0-47ec-9144-5bd829af26fc

📥 Commits

Reviewing files that changed from the base of the PR and between 98d3002 and ffb022f.

📒 Files selected for processing (2)
  • packages/storage/src/secure-store/secure-store.ts
  • scripts/bun-test-manifest-data-storage.ts
📝 Walkthrough

Walkthrough

Adds a security.disableOsKeyring setting and environment override. Storage now classifies keyring errors, latches denied or locked sessions, emits one warning, guards native calls, and hardens encrypted-file fallback and machine-secret handling.

Changes

OS keyring session handling

Layer / File(s) Summary
Configuration and runtime bridge
packages/cli/src/config/..., packages/storage/src/index.ts, schemas/settings.schema.json, docs/cli/configuration.md
Adds security.disableOsKeyring, its default and documentation, and propagates the resolved value to storage before runtime setup.
Error classification and session state
packages/storage/src/secure-store/classify-error.ts, packages/storage/src/secure-store/keyring-session-state.ts
Classifies native keyring failures and maintains process-wide opt-out, disablement, warning, and reset state.
Guarded adapter and error propagation
packages/storage/src/secure-store/default-keyring-adapter.ts, packages/storage/src/secure-store/secure-store.ts, packages/storage/src/secure-store/*test.ts
Guards native operations, prevents adapter creation after disablement, preserves classifications, and updates transient-failure tests.
Machine-secret and fallback hardening
packages/storage/src/secure-store/secure-store.ts, packages/storage/test-bun/*, packages/storage/bunfig.toml, packages/storage/test-setup-bun-session-reset.ts, scripts/bun-test-manifest-data-storage.ts
Separates read and write machine-secret resolution, protects v2 envelopes from orphaning, and adds comprehensive session, opt-out, durability, and isolation tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR meets the session-latch and opt-out objectives, but native read-path error fidelity remains explicitly deferred to issue #3067. Implement native read-path OSStatus preservation and the related setter behavior, or split and track the remaining requirements before closing issue #2928.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the keyring prompt-storm fix and the new OS keyring opt-out.
Description check ✅ Passed The description includes all template sections and provides detailed scope, testing steps, results, limitations, and linked issue information.
Out of Scope Changes check ✅ Passed The configuration, storage, durability, test, and documentation changes directly support the linked issue objectives.
Docstring Coverage ✅ Passed Docstring coverage is 87.10% which is sufficient. The required threshold is 80.00%.
✨ 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 issue2928

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

@github-actions github-actions Bot added the maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run label Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR changes 18 file(s).

  • packages/storage/src/secure-store/secure-store.ts: (per-file summary unavailable)
  • packages/cli/src/config/settings-schema/schema-extensions.ts: (per-file summary unavailable)
  • schemas/settings.schema.json: (per-file summary unavailable)
  • packages/storage/bunfig.toml: (per-file summary unavailable)
  • packages/storage/src/secure-store/secure-store.fallback.test.ts: (per-file summary unavailable)
  • packages/cli/src/config/postConfigRuntime.ts: (per-file summary unavailable)
  • project-plans/issue2928/PLAN.md: (per-file summary unavailable)
  • packages/storage/src/secure-store/default-keyring-adapter.ts: (per-file summary unavailable)
  • packages/storage/test-bun/secure-store.keyring-session.bun.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest-data-storage.ts: (per-file summary unavailable)
  • packages/storage/src/secure-store/keyring-session-state.ts: (per-file summary unavailable)
  • packages/storage/src/secure-store/classify-error.ts: (per-file summary unavailable)
  • packages/cli/src/config/settings.test.ts: (per-file summary unavailable)
  • packages/storage/src/secure-store/provider-key-storage.test.ts: (per-file summary unavailable)
  • packages/storage/test-setup-bun-session-reset.ts: (per-file summary unavailable)
  • docs/cli/configuration.md: (per-file summary unavailable)
  • packages/storage/test-bun/secure-store.fallback-hardening.bun.ts: (per-file summary unavailable)
  • packages/storage/src/index.ts: (per-file summary unavailable)

Changes

Layer File(s) Summary
packages/storage/src/secure-store packages/storage/src/secure-store/secure-store.ts, packages/storage/src/secure-store/secure-store.fallback.test.ts, packages/storage/src/secure-store/default-keyring-adapter.ts, packages/storage/src/secure-store/keyring-session-state.ts, packages/storage/src/secure-store/classify-error.ts, packages/storage/src/secure-store/provider-key-storage.test.ts Changes in packages/storage/src/secure-store
packages/cli/src/config/settings-schema packages/cli/src/config/settings-schema/schema-extensions.ts Changes in packages/cli/src/config/settings-schema
schemas schemas/settings.schema.json Changes in schemas
packages/storage packages/storage/bunfig.toml, packages/storage/test-setup-bun-session-reset.ts Changes in packages/storage
packages/cli/src/config packages/cli/src/config/postConfigRuntime.ts, packages/cli/src/config/settings.test.ts Changes in packages/cli/src/config
project-plans/issue2928 project-plans/issue2928/PLAN.md Changes in project-plans/issue2928
packages/storage/test-bun packages/storage/test-bun/secure-store.keyring-session.bun.ts, packages/storage/test-bun/secure-store.fallback-hardening.bun.ts Changes in packages/storage/test-bun
scripts scripts/bun-test-manifest-data-storage.ts Changes in scripts
docs/cli docs/cli/configuration.md Changes in docs/cli
packages/storage/src packages/storage/src/index.ts Changes in packages/storage/src

Magnitude

🎯 3 (L)
1903 additions, 98 deletions, 18 changed files across 2 packages, 0 acceptance criteria

Related

No related items found.


Walkthrough generated by LLxprt PR Review. Planner issue: #2256

Comment thread packages/storage/test-bun/secure-store.keyring-session.bun.ts Outdated
Comment thread packages/storage/src/secure-store/keyring-session-state.ts
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

OpenCodeReview — automatic reviews suspended

Automatic OCR reviews are suspended for this PR after 2 of 2 automatic reviews.

To get more reviews you can:

  • Check the box below to re-enable automatic reviews (resets the counter), or

  • Comment /review, /ocr, or /open-code-review to request a single review on demand.

  • Re-enable automatic reviews

CI caught two consequences of adding security.disableOsKeyring:

- scripts shard: generate-settings-doc's check mode failed because
  docs/cli/configuration.md and schemas/settings.schema.json were not
  regenerated. Ran npm run docs:settings; both gain only the new boolean.
- cli shard: settings.test.ts asserts the fully-defaulted security block
  with toStrictEqual, so it needed the new key.

Also addresses two review comments on the PR:

- The "env var wins when both are present" wording was wrong.
  isOsKeyringSessionDisabled() ORs the latch, the setting and the env var,
  so there is no precedence -- each independently disables the keyring.
  Corrected the comments and rewrote the test to assert what actually
  holds: either path alone disables, and with neither set the session is
  enabled.
- Wrapped the process.stderr.write spy in try/finally so a failing
  assertion cannot leave it attached and pollute later tests.
Comment thread packages/storage/test-bun/secure-store.keyring-session.bun.ts
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI N/A% N/A% N/A% N/A%
Core N/A% N/A% N/A% N/A%
CLI Package - Full Text Report
CLI full-text-summary.txt not found at: coverage_cli/packages/cli/coverage/full-text-summary.txt
Core Package - Full Text Report
Core full-text-summary.txt not found at: coverage_core/packages/core/coverage/full-text-summary.txt

For detailed HTML reports, please see the 'coverage-reports-24.x-ubuntu-latest' artifact from the main CI run.

env.set(undefined) read ambiguously enough that review took it for
vitest's helper and flagged it as possibly assigning the literal string
"undefined". It was a local helper that already deleted the key, but the
ambiguity is worth removing: set() now accepts only a string, and an
explicit clear() performs the delete, so passing undefined is no longer
expressible.
# Conflicts:
#	packages/storage/src/secure-store/default-keyring-adapter.ts
@acoliver

acoliver commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Merged latest main, which now contains #3074 (Fixes #3020). That PR shipped the same LLXPRT_DISABLE_OS_KEYRING=1 escape hatch, so there was a genuine conflict in default-keyring-adapter.ts.

Resolved by keeping one source of truth. #3074 read the env var through a local isOsKeyringDisabled() in default-keyring-adapter.ts; this branch reads the same variable in keyring-session-state.ts, where it sits alongside the security.disableOsKeyring setting and the runtime DENIED/LOCKED latch. The factory now calls isOsKeyringSessionDisabled(), which ORs all three, and #3074's local const plus helper were removed so the variable is not defined in two places.

Behaviour from #3074 is preserved and now strictly extended: the env var alone still short-circuits the factory before @napi-rs/keyring is imported. Its test suite (test-bun/keyring-opt-out.bun.ts) is untouched and passes alongside this branch's suite -- 29/29 across both files.

Full verification re-run on the merged head: format, typecheck, lint, lint:eslint-guard and build all exit 0; storage bun and vitest suites both green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/storage/test-bun/secure-store.keyring-session.bun.ts (2)

223-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct the fixedMachineSecret name and doc.

The function returns a new random 32-byte value on each call. The name and the doc claim a fixed, deterministic secret. Each caller stores the result in a local variable, so the tests are correct, but the doc is wrong. Rename to randomMachineSecret and state that the value is stable per test because callers reuse the variable.

♻️ Proposed fix
-/** A fixed 32-byte machine secret for deterministic v:2 envelopes. */
-function fixedMachineSecret(): Buffer {
+/**
+ * A fresh random 32-byte machine secret. Each test holds the returned value in
+ * a local variable, so the v:2 envelope and its read-back use the same secret.
+ */
+function randomMachineSecret(): Buffer {
   return crypto.randomBytes(32);
 }
🤖 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 `@packages/storage/test-bun/secure-store.keyring-session.bun.ts` around lines
223 - 226, Rename fixedMachineSecret to randomMachineSecret and update its
documentation to describe that it generates a random 32-byte secret, with
stability per test provided by callers reusing the returned local variable.
Update all references to the renamed function.

631-655: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the comment with the mechanism it uses.

The comment says the test simulates a DENIED latch, but the test calls setOsKeyringDisabledBySetting(true), which sets the settings opt-out flag. The assertion is still valid because the guard reads isOsKeyringSessionDisabled(). State that the test drives the shared disablement predicate through the setting, so the comment does not imply DENIED-latch coverage that the second test at line 657 provides.

🤖 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 `@packages/storage/test-bun/secure-store.keyring-session.bun.ts` around lines
631 - 655, Update the comments in the test around createGuardedAdapter to state
that setOsKeyringDisabledBySetting(true) drives the shared
isOsKeyringSessionDisabled() predicate through the settings opt-out, rather than
simulating a DENIED latch. Keep the existing assertions and distinguish this
coverage from the separate DENIED-latch test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/storage/bunfig.toml`:
- Line 2: Update the storage Bun manifest’s preload configuration to include
test-setup-bun-session-reset.ts alongside the existing preload entries, ensuring
scripts/run_bun_tests.ts forwards the session reset setup for every storage test
file.

In `@packages/storage/src/secure-store/secure-store.ts`:
- Around line 206-229: Move the read-only JSDoc block with requirement R3.5 from
above loadMachineSecretForWrite to immediately above loadMachineSecretForRead.
Leave the write-path JSDoc solely documenting loadMachineSecretForWrite,
preserving each method’s existing implementation and annotations.

---

Nitpick comments:
In `@packages/storage/test-bun/secure-store.keyring-session.bun.ts`:
- Around line 223-226: Rename fixedMachineSecret to randomMachineSecret and
update its documentation to describe that it generates a random 32-byte secret,
with stability per test provided by callers reusing the returned local variable.
Update all references to the renamed function.
- Around line 631-655: Update the comments in the test around
createGuardedAdapter to state that setOsKeyringDisabledBySetting(true) drives
the shared isOsKeyringSessionDisabled() predicate through the settings opt-out,
rather than simulating a DENIED latch. Keep the existing assertions and
distinguish this coverage from the separate DENIED-latch test.
🪄 Autofix

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: 3d5b2f58-f6d3-4a22-94c7-cf35bbd0deef

📥 Commits

Reviewing files that changed from the base of the PR and between 1944fa0 and 98d3002.

⛔ Files ignored due to path filters (1)
  • project-plans/issue2928/PLAN.md is excluded by !project-plans/**
📒 Files selected for processing (17)
  • docs/cli/configuration.md
  • packages/cli/src/config/postConfigRuntime.ts
  • packages/cli/src/config/settings-schema/schema-extensions.ts
  • packages/cli/src/config/settings.test.ts
  • packages/storage/bunfig.toml
  • packages/storage/src/index.ts
  • packages/storage/src/secure-store/classify-error.ts
  • packages/storage/src/secure-store/default-keyring-adapter.ts
  • packages/storage/src/secure-store/keyring-session-state.ts
  • packages/storage/src/secure-store/provider-key-storage.test.ts
  • packages/storage/src/secure-store/secure-store.fallback.test.ts
  • packages/storage/src/secure-store/secure-store.ts
  • packages/storage/test-bun/secure-store.fallback-hardening.bun.ts
  • packages/storage/test-bun/secure-store.keyring-session.bun.ts
  • packages/storage/test-setup-bun-session-reset.ts
  • schemas/settings.schema.json
  • scripts/bun-test-manifest-data-storage.ts

Comment thread packages/storage/bunfig.toml
Comment thread packages/storage/src/secure-store/secure-store.ts Outdated
The Bun-only reset hooks were wired into packages/storage/bunfig.toml, but
scripts/run_bun_tests.ts builds explicit --preload arguments from the
manifest entry's preloads and never reads that file. In manifest-driven
runs -- which is what npm test and CI use -- the session reset was
silently dropped, so the process-wide keyring latch could leak from one
test file into the next. The suites masked it because each one already
resets the latch in its own beforeEach.

The storage manifest entry now declares both preloads.

Also moves the read-path JSDoc back onto loadMachineSecretForRead. It was
orphaned above loadMachineSecretForWrite when the R3.4 machine-secret
mirror was dropped, leaving two stacked blocks on one method and none on
the other.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SecureStore cannot distinguish Keychain denial from absence: native error collapse, no session degrade, no keyring opt-out

1 participant