Skip to content

feat: add host-capability probes for sandboxed environments#143

Open
nicknisi wants to merge 5 commits intomainfrom
nick/host-capability-probe
Open

feat: add host-capability probes for sandboxed environments#143
nicknisi wants to merge 5 commits intomainfrom
nick/host-capability-probe

Conversation

@nicknisi
Copy link
Copy Markdown
Member

@nicknisi nicknisi commented May 4, 2026

Summary

  • Adds src/lib/host-probe.ts with proactive and reactive sandbox detection for non-interactive environments
  • When the CLI runs inside an AI agent sandbox where the keyring or home directory is unavailable, it now emits a single actionable warning per session instead of letting opaque EPERM/EACCES errors propagate
  • Two mechanisms:
    • warnIfSandboxed() probes home-fs and keychain upfront in ensureAuthenticated()
    • observeHostFailure() is wired into the keyring read/write catch blocks in credential-store.ts and config-store.ts
  • All three probe paths (probeHomeFs, probeKeychain, observeHostFailure) gate on isPermissionError, so transient errors (ENOSPC, EIO, user-canceled keychain prompt, daemon hiccups) never produce a false-positive sandbox warning
  • Probe entry "not found" is correctly treated as a healthy keychain
  • Both paths share a warnedThisSession flag so agents see exactly one message regardless of how many operations fail

Review & Testing Checklist for Human

  • Manual: chmod 000 ~/.workos && echo "" | workos org list 2>&1 should print the sandbox warning, then chmod 700 ~/.workos to restore
  • Manual: in a normal (interactive) terminal, confirm no spurious sandbox warning appears during workos auth login or workos org list
  • Confirm only one warning is emitted per session even when multiple keyring operations fail

Notes

Bugs identified by Devin Review and Greptile across review rounds were fixed in this PR:

  • probeKeychain() always reported failure because getPassword() throws "not found" for the probe entry — now treated as healthy
  • require() in an ESM module — replaced with a static import { Entry } from '@napi-rs/keyring'
  • Synchronous fs calls — switched to fs/promises and await
  • /sandbox/i matched any substring — narrowed to /\bsandboxd?\b/i
  • node:crypto import violated CLAUDE.md — replaced with global crypto.randomUUID()
  • probeHomeFs and probeKeychain flagged non-permission errors as sandbox failures — both now gate on isPermissionError to match observeHostFailure

Test suite: 1637 tests passing (16 in host-probe.spec.ts covering the new behaviors). Lint, format, typecheck, and build all clean.

Link to Devin session: https://app.devin.ai/sessions/da014acc49524fe9b12ed04a3f5f0406
Requested by: @nicknisi

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
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Sandbox & Host-Capability Detection

Layer / File(s) Summary
Core Types & Probe State
src/lib/host-probe.ts
Adds exported HostCapability, ProbeFailure, ProbeResult, and per-session state (warnedThisSession, cachedProbe). Defines permission-detection patterns and helper predicates (isPermissionError, isMissingEntryError).
Probe Implementations
src/lib/host-probe.ts
Adds probeHomeFs() (create/delete temp file under ~/.workos) and probeKeychain() (keyring Entry access; treats missing entry as non-failure).
Probe Aggregation & Caching
src/lib/host-probe.ts
Adds runHostProbe() to execute probes, collect failures, and cache the ProbeResult per session.
Warnings & Reactive Observation
src/lib/host-probe.ts
Adds warnIfSandboxed() to emit a single per-session warning in non-interactive environments when probes fail, observeHostFailure(capability, error) to emit targeted warnings on permission-like errors, and _resetProbeState() to clear cached state.
Integration: Keychain Failure Hooks
src/lib/config-store.ts, src/lib/credential-store.ts
Import observeHostFailure and call observeHostFailure('keychain', error) from keyring read/write catch blocks (in addition to existing warning logs).
Integration: Proactive Auth Warning
src/lib/ensure-auth.ts
Import warnIfSandboxed and invoke await warnIfSandboxed() at the start of ensureAuthenticated().
Tests
src/lib/host-probe.spec.ts
Adds Vitest suite that mocks fs, os.homedir, keyring, and environment interactivity; tests probe success/failure, caching, warn-once behavior, interactive vs non-interactive gating, and observeHostFailure filtering and duplicate-warning prevention.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% 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
Title check ✅ Passed The PR title 'feat: add host-capability probes for sandboxed environments' accurately summarizes the main change: adding host-probe functionality to detect and warn about sandboxed/non-interactive environments.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ 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 nick/host-capability-probe

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 4, 2026

Greptile Summary

This 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 EPERM/EACCES errors.

  • src/lib/host-probe.ts: New module with runHostProbe() (probes home-fs write and keychain read at startup), warnIfSandboxed() (called from ensureAuthenticated()), and observeHostFailure() (reactive catch-site hook); both paths share a warnedThisSession flag to enforce warn-once semantics. The isPermissionError guard is consistently applied in both probeHomeFs and probeKeychain, addressing the false-positive concerns raised in earlier review iterations.
  • src/lib/credential-store.ts / config-store.ts: observeHostFailure('keychain', error) added to readFromKeyring and writeToKeyring catch blocks in both stores.
  • src/lib/host-probe.spec.ts: 9 unit tests covering probe success, home-fs and keychain failure detection, caching, warn-once behavior, interactive-mode suppression, and the sandboxes-substring false-positive regression.

Confidence Score: 5/5

The 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

Filename Overview
src/lib/host-probe.ts New module introducing proactive (runHostProbe/warnIfSandboxed) and reactive (observeHostFailure) sandbox detection; ESM imports are correct, isPermissionError guard is applied in both probeHomeFs and probeKeychain, and the session-scoped warn-once flag works correctly. Minor: probe file not cleaned up via finally block when unlink itself fails.
src/lib/credential-store.ts Adds observeHostFailure('keychain', error) to readFromKeyring and writeToKeyring catch blocks; deleteFromKeyring correctly left unchanged as delete failures are less indicative of sandboxing.
src/lib/config-store.ts Mirrors credential-store.ts changes: adds observeHostFailure to keyring read/write failures; deleteFromKeyring intentionally unchanged.
src/lib/ensure-auth.ts Inserts await warnIfSandboxed() at the top of ensureAuthenticated(), before credential checks; placement is correct for the proactive detection flow.
src/lib/host-probe.spec.ts 9 well-structured tests covering probe success/failure, caching, warn-once semantics, and the sandboxes substring false-positive regression; mocks are correctly isolated per test via _resetProbeState.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Command
    participant EA as ensureAuthenticated()
    participant HP as host-probe.ts
    participant CS as credential-store / config-store
    participant KR as @napi-rs/keyring
    participant FS as node:fs

    CLI->>EA: ensureAuthenticated()
    EA->>HP: warnIfSandboxed()
    HP->>HP: check warnedThisSession / isNonInteractiveEnvironment
    HP->>HP: runHostProbe() [cached]
    HP->>FS: mkdir + writeFile + unlink (~/.workos probe)
    FS-->>HP: ok or EPERM/EACCES
    HP->>KR: new Entry('workos-cli','probe').getPassword()
    KR-->>HP: null / not-found / EPERM
    HP-->>EA: ProbeResult {ok, failures}
    alt probe failed and non-interactive
        HP->>CLI: logWarn(Host capabilities may be unavailable)
    end
    EA->>CS: getCredentials()
    CS->>KR: entry.getPassword()
    alt keyring throws permission error
        CS->>HP: observeHostFailure('keychain', error)
        HP->>HP: check warnedThisSession (skip if already warned)
        HP->>CLI: logWarn(Host capability failed)
    end
    CS-->>EA: Credentials or null
Loading

Reviews (5): Last reviewed commit: "fix(host-probe): gate probeKeychain on p..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

- 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>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: nick.nisi@workos.com <nick.nisi@workos.com>
Comment thread src/lib/host-probe.ts
Comment on lines +61 to +64
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
return { capability: 'home-fs', detail };
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 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.

Suggested change
} 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 };
}

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.

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>
devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant