feat: add host-capability probes for sandboxed environments#143
feat: add host-capability probes for sandboxed environments#143
Conversation
When the CLI runs inside an AI agent sandbox (Claude Code, Codex, Cursor), the keyring and home directory may be unavailable. Instead of letting opaque EPERM errors confuse the agent, we now: 1. Proactively probe home-fs and keychain on first auth check in non-interactive mode (warnIfSandboxed in ensure-auth) 2. Reactively observe permission errors in keyring read/write calls in both credential-store and config-store (observeHostFailure) 3. Emit a single actionable warning per session pointing the user to re-run on the host shell
📝 WalkthroughWalkthroughAdds a host-capability probing system with per-session caching and warnings for non-interactive/sandboxed environments; integrates runtime keychain-failure observation into credential/config stores and triggers a sandbox warning at the start of authentication. Tests verify probe behavior and warning semantics. ChangesSandbox & Host-Capability Detection
🚥 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 docstrings
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR adds proactive and reactive sandbox detection for non-interactive CLI environments (AI agents, CI), emitting a single actionable warning per session when the keyring or home directory is inaccessible instead of surfacing opaque
Confidence Score: 5/5The change is additive and isolated — it wraps existing keyring catch blocks and fires a one-time warning; no auth or storage logic is altered. All three issues flagged in earlier review rounds have been addressed: ESM-safe imports replace the require call, probeHomeFs now gates on isPermissionError, and probeKeychain applies the same guard. The warn-once flag correctly deduplicates across both detection paths. The only remaining gap is that the probe file can be orphaned when unlink itself throws, which is an unlikely edge case with no correctness impact. src/lib/host-probe.ts — specifically the probeHomeFs try block, which lacks a finally-based cleanup for the probe file. Important Files Changed
|
- probeKeychain() no longer reports the keychain as failed when the
probe entry is simply absent. A 'not found' / 'No such' error from
@napi-rs/keyring now indicates a healthy keychain (matches the
existing pattern in deleteFromKeyring in config-store and
credential-store), so non-interactive runs on healthy hosts no longer
emit false-positive sandbox warnings.
- Replace require('@napi-rs/keyring') with a static ES import. The
package has 'type: module', so the previous require() threw
ReferenceError at runtime and caused probeKeychain to always fail.
- Switch probeHomeFs to node:fs/promises and make runHostProbe and
warnIfSandboxed async, per the project's no-sync-fs guideline.
- Tighten the /sandbox/i permission pattern to /\bsandboxd?\b/i so
unrelated error messages containing 'sandbox' as a substring don't
trigger sandbox warnings.
Updates the spec to drive the async API and adds coverage for the
healthy-keychain (entry-absent) and substring-collision cases.
Co-Authored-By: nick.nisi@workos.com <nick.nisi@workos.com>
Co-Authored-By: nick.nisi@workos.com <nick.nisi@workos.com>
| } catch (error) { | ||
| const detail = error instanceof Error ? error.message : String(error); | ||
| return { capability: 'home-fs', detail }; | ||
| } |
There was a problem hiding this comment.
probeHomeFs catches every exception and converts it to a ProbeFailure, so non-permission errors like ENOSPC (disk full) or EIO (I/O error on a network drive) will cause warnIfSandboxed to print "This may be a sandboxed environment." — which is wrong and confusing. observeHostFailure correctly gates on isPermissionError; probeHomeFs should do the same so the two paths stay consistent.
| } catch (error) { | |
| const detail = error instanceof Error ? error.message : String(error); | |
| return { capability: 'home-fs', detail }; | |
| } | |
| } catch (error) { | |
| if (!isPermissionError(error)) return null; | |
| const detail = error instanceof Error ? error.message : String(error); | |
| return { capability: 'home-fs', detail }; | |
| } |
There was a problem hiding this comment.
Good catch — fixed in 7f9e0e6. probeHomeFs now mirrors observeHostFailure and only treats permission-class errors (isPermissionError) as sandbox indicators, so ENOSPC/EIO no longer produce misleading "sandboxed environment" warnings. Added a regression test (does not flag non-permission home-fs errors as sandbox failures).
probeHomeFs previously treated every fs error as a sandbox indicator, so transient errors like ENOSPC or EIO would emit a misleading "sandboxed environment" warning. Gate the catch block on isPermissionError so it stays consistent with observeHostFailure. Co-Authored-By: nick.nisi@workos.com <nick.nisi@workos.com>
probeKeychain previously treated any non-missing-entry keychain error as a sandbox indicator. A user-canceled macOS keychain prompt or a transient keyring daemon error would therefore produce a misleading "sandboxed environment" warning on healthy hosts. Mirror probeHomeFs and observeHostFailure by ignoring non-permission errors. Co-Authored-By: nick.nisi@workos.com <nick.nisi@workos.com>
Summary
src/lib/host-probe.tswith proactive and reactive sandbox detection for non-interactive environmentswarnIfSandboxed()probes home-fs and keychain upfront inensureAuthenticated()observeHostFailure()is wired into the keyring read/write catch blocks incredential-store.tsandconfig-store.tsprobeHomeFs,probeKeychain,observeHostFailure) gate onisPermissionError, so transient errors (ENOSPC, EIO, user-canceled keychain prompt, daemon hiccups) never produce a false-positive sandbox warningwarnedThisSessionflag so agents see exactly one message regardless of how many operations failReview & Testing Checklist for Human
chmod 000 ~/.workos && echo "" | workos org list 2>&1should print the sandbox warning, thenchmod 700 ~/.workosto restoreworkos auth loginorworkos org listNotes
Bugs identified by Devin Review and Greptile across review rounds were fixed in this PR:
probeKeychain()always reported failure becausegetPassword()throws "not found" for the probe entry — now treated as healthyrequire()in an ESM module — replaced with a staticimport { Entry } from '@napi-rs/keyring'fs/promisesandawait/sandbox/imatched any substring — narrowed to/\bsandboxd?\b/inode:cryptoimport violated CLAUDE.md — replaced with globalcrypto.randomUUID()probeHomeFsandprobeKeychainflagged non-permission errors as sandbox failures — both now gate onisPermissionErrorto matchobserveHostFailureTest suite: 1637 tests passing (16 in
host-probe.spec.tscovering the new behaviors). Lint, format, typecheck, and build all clean.Link to Devin session: https://app.devin.ai/sessions/da014acc49524fe9b12ed04a3f5f0406
Requested by: @nicknisi