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:

  1. Check if op (1Password CLI) command exists
  2. Check if glab appears in op plugin list
  3. If both true, invoke: op plugin run -- glab auth credential-helper
  4. 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 with GLAB_CREDENTIAL_HELPER_SKIP_WRAPPER env 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() - Verifies op command exists and glab is 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=1 to 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

Closes #8161

Edited by Kai Armstrong

Merge request reports

Loading