feat: detect and use shell wrappers in credential helper
Summary
This MR enables glab's credential helper to automatically work with 1Password when invoked directly by external tools (GitLab LSP, Duo CLI, etc.).
Problem
When external tools invoke the glab binary directly (e.g., /usr/local/bin/glab or via Node.js spawnSync('glab', ...)), they bypass shell aliases. This causes credential helpers to fail for users who rely on wrappers like 1Password Shell Plugin that inject credentials.
Current behavior:
- User has:
alias glab="op plugin run -- glab"in their shell - LSP calls:
spawnSync('glab', ['auth', 'credential-helper']) - Node.js finds binary directly, bypasses shell alias
- Result: 401 Unauthorized
❌
Solution
The credential helper now automatically detects if 1Password CLI is installed and has the GitLab plugin available. When credentials are not found through normal means (config, keyring, env vars), it re-invokes itself through 1Password's wrapper.
Detection method:
- Check if
op(1Password CLI) command exists - Check if
glabappears inop plugin list - If both true, invoke:
op plugin run -- glab auth credential-helper - Parse and return credentials from the wrapped invocation
Flow:
LSP: spawnSync('glab', ['auth', 'credential-helper'])
↓
Executes: /usr/local/bin/glab (no alias)
↓
Glab finds no credentials → Unauthenticated
↓
Detects: op CLI installed + glab plugin available ✅
↓
Re-invokes: op plugin run -- glab auth credential-helper
↓
1Password injects GITLAB_TOKEN ✅
↓
Returns credentials successfully
Key Benefits
-
✅ Transparent: All clients benefit automatically (no changes needed in LSP, Duo CLI, etc.) -
✅ Direct detection: Checks for 1Password explicitly, not shell aliases -
✅ Reliable: Works even when called without a shell (spawnSync, direct binary execution) -
✅ Safe: Prevents infinite recursion withGLAB_CREDENTIAL_HELPER_SKIP_WRAPPERenv var -
✅ Fast: Simple checks instead of trying multiple shell strategies -
✅ Extensible: Easy to add other credential managers in the future
Implementation Details
New functions:
-
detectGlabWrapper()- Checks if 1Password CLI is available with GitLab plugin -
has1PasswordGlabPlugin()- Verifiesopcommand exists andglabis in plugin list -
tryWrappedInvocation()- Executes wrapper and parses response
Key implementation notes:
- When wrapped invocation returns JSON, we parse it as a generic map to avoid custom unmarshaling issues
- Wrapper receives
GLAB_CREDENTIAL_HELPER_SKIP_WRAPPER=1to prevent infinite recursion - Includes stderr in error messages for better debugging
Modified behavior:
- When
Unauthenticated, attempts wrapper detection before returning error - Falls back to standard error message if no wrapper is detected
Testing
- Added test for recursion prevention mechanism
- All existing tests pass
- Manually tested with 1Password Shell Plugin - successfully retrieves credentials
Alternative Approaches Considered
Client-side detection (as proposed in gitlab-org/editor-extensions/gitlab-lsp!2924 (closed)):
- Requires each client to implement wrapper detection
- Code duplication across multiple codebases
- Doesn't help future clients
Shell alias detection:
- Complex (must source rc files, enable bash alias expansion)
- Fragile (depends on shell configuration)
- Slower (tries multiple shell strategies)
- Doesn't work in non-interactive contexts
This approach was chosen because it:
- Fixes the issue at the source (glab itself)
- Benefits all clients automatically
- Directly detects the credential manager rather than parsing shell configuration
- Works reliably in non-interactive contexts
Related
Closes #8161