Resolving GPG-Agent Connectivity Issues: When gpg-agent Reports Running but GPG Can’t Detect It


2 views

This issue stems from the fundamental disconnect between how gpg-agent (v2.0.19) and gpg (v1.4.13) detect agent availability:

# gpg-agent checks socket connection
socket(PF_FILE, SOCK_STREAM, 0) = 5
connect(5, {sa_family=AF_FILE, sun_path="/home/user/.gnupg/S.gpg-agent"}, 32) = 0

# gpg only checks environment variables
infostr = getenv("GPG_AGENT_INFO")
if (!infostr || !*infostr) {
    log_error(_("gpg-agent is not available in this session\n"));
    return SPWQ_NO_AGENT;
}

When working with mixed gpg/gpg-agent versions, you'll encounter these scenarios:

  • Agent running in Terminal A but invisible to Terminal B
  • Stale GPG_AGENT_INFO environment variables
  • Silent failures during batch processing

Here's a bash function that properly tests agent connectivity:

function ensure_gpg_agent() {
    # First check socket connectivity
    local socket_path="${GNUPGHOME:-$HOME/.gnupg}/S.gpg-agent"
    if [[ -S "$socket_path" ]]; then
        local test_response
        test_response=$(echo "GETINFO pid" | nc -U "$socket_path" 2>/dev/null)
        [[ "$test_response" =~ ^OK ]] && return 0
    fi

    # Fallback to environment if socket fails
    if [[ -n "$GPG_AGENT_INFO" ]]; then
        local port=${GPG_AGENT_INFO##*:}
        [[ "$port" =~ ^[0-9]+$ ]] && \
            echo "GETINFO pid" | nc localhost "$port" | grep -q ^OK && return 0
    fi

    # Start new agent if all checks fail
    eval "$(gpg-agent --daemon)"
    export GPG_AGENT_INFO
    return $?
}

For legacy systems where you must use gpg 1.4.x:

# Force environment update
find /tmp -maxdepth 1 -name 'gpg-agent*' -type f -mmin -5 \
    -exec cat {} \; | grep GPG_AGENT_INFO | source /dev/stdin

# Alternative using gpgconf
if command -v gpgconf &>/dev/null; then
    export GPG_AGENT_INFO=$(gpgconf --list-dirs agent-socket)
    gpgconf --launch gpg-agent
fi

For critical batch processing systems, implement this comprehensive check:

function verify_gpg_connectivity() {
    local timeout=2
    local test_msg="GETINFO version"
    
    # Try socket first
    local socket="${GNUPGHOME:-$HOME/.gnupg}/S.gpg-agent"
    if [[ -S "$socket" ]]; then
        if echo "$test_msg" | timeout "$timeout" socat - UNIX-CONNECT:"$socket" | grep -q ^OK; then
            return 0
        fi
    fi
    
    # Try TCP next
    if [[ "$GPG_AGENT_INFO" =~ :([0-9]+)$ ]]; then
        local port="${BASH_REMATCH[1]}"
        if echo "$test_msg" | timeout "$timeout" nc localhost "$port" | grep -q ^OK; then
            return 0
        fi
    fi
    
    # Final fallback
    if ! pgrep -x gpg-agent &>/dev/null; then
        eval "$(gpg-agent --daemon)"
        return $?
    fi
    
    return 1
}

This handles all edge cases including stale sockets, zombie processes, and network connectivity issues.

When issues persist, use these diagnostic commands:

# Check active sockets
ls -la ${GNUPGHOME:-$HOME/.gnupg}/S.gpg-agent*

# Verify agent responsiveness
echo "GETINFO pid" | socat - UNIX-CONNECT:${GNUPGHOME:-$HOME/.gnupg}/S.gpg-agent

# Environment inspection
gpgconf --list-dirs
gpg-connect-agent --dirmngr 'getinfo homeserver' /bye

When working with GPG encryption in shell scripts, you might encounter this frustrating scenario where gpg-agent reports an existing instance while gpg fails to recognize it. This typically occurs when:

# Terminal 1
$ eval $(gpg-agent --daemon)

# Terminal 2 (new session)
$ eval $(gpg-agent --daemon)
gpg-agent[21927]: a gpg-agent is already running - not starting a new one
$ gpg -d file.asc
gpg: gpg-agent is not available in this session

The root cause lies in how each component detects the agent:

  • gpg-agent: Checks for active socket connections at ~/.gnupg/S.gpg-agent
  • gpg: Relies solely on the GPG_AGENT_INFO environment variable

This becomes problematic when working across different terminal sessions where environment variables aren't shared.

Here are three robust approaches to handle this situation:

Method 1: Socket-based Verification

This method directly checks the agent socket:

# Check for active gpg-agent socket
if [ -S "${HOME}/.gnupg/S.gpg-agent" ]; then
    # Test socket communication
    if gpg-connect-agent --quiet /bye 2>/dev/null; then
        echo "gpg-agent is available"
    else
        # Socket exists but agent isn't responding
        rm -f "${HOME}/.gnupg/S.gpg-agent"
        eval $(gpg-agent --daemon)
    fi
else
    eval $(gpg-agent --daemon)
fi

Method 2: Environment Variable Management

For scripts that need to work across sessions:

# Store agent info in a known location
AGENT_ENV="${HOME}/.gpg-agent-info"

if [ -f "${AGENT_ENV}" ]; then
    . "${AGENT_ENV}"
    if ! kill -0 $(echo ${GPG_AGENT_INFO} | cut -d: -f2) 2>/dev/null; then
        # PID is invalid, start new agent
        eval $(gpg-agent --daemon --write-env-file "${AGENT_ENV}")
    fi
else
    eval $(gpg-agent --daemon --write-env-file "${AGENT_ENV}")
fi

Method 3: Modern GPG 2.1+ Approach

For newer GPG versions that use the standard socket path:

# For GPG 2.1+ with automatic socket detection
export GPG_TTY=$(tty)
if ! gpg-connect-agent --quiet /bye 2>/dev/null; then
    gpgconf --launch gpg-agent
fi

When dealing with both old and new GPG versions:

function ensure_gpg_agent() {
    # Try modern approach first
    if gpg-connect-agent --quiet /bye 2>/dev/null; then
        return 0
    fi
    
    # Fallback to legacy method
    if [ -S "${HOME}/.gnupg/S.gpg-agent" ]; then
        GPG_AGENT_INFO=$(find "${HOME}/.gnupg" -name 'S.gpg-agent' -printf '%p:%l:1')
        export GPG_AGENT_INFO
        return 0
    fi
    
    # Start new agent
    eval $(gpg-agent --daemon)
    return $?
}
  • Always verify agent connectivity before batch operations
  • Handle both socket and environment variable cases
  • Implement proper cleanup in error cases
  • Consider using gpgconf for modern GPG installations
  • Log agent status changes for debugging

Here's a complete solution combining these approaches:

#!/bin/bash

# Initialize GPG agent with comprehensive checks
init_gpg_agent() {
    # Modern GPG 2.1+ detection
    if command -v gpgconf &>/dev/null; then
        export GPG_TTY=$(tty)
        gpgconf --launch gpg-agent 2>/dev/null
        return $?
    fi

    # Legacy GPG detection
    local agent_socket="${HOME}/.gnupg/S.gpg-agent"
    local agent_env="${HOME}/.gpg-agent-info"
    
    # Check existing socket
    if [ -S "${agent_socket}" ]; then
        if gpg-connect-agent --quiet /bye 2>/dev/null; then
            return 0
        fi
        rm -f "${agent_socket}"
    fi
    
    # Check existing env file
    if [ -f "${agent_env}" ]; then
        . "${agent_env}" &>/dev/null
        if [ -n "${GPG_AGENT_INFO}" ]; then
            local pid=$(echo "${GPG_AGENT_INFO}" | cut -d: -f2)
            if kill -0 "${pid}" 2>/dev/null; then
                return 0
            fi
        fi
    fi
    
    # Start new agent
    eval $(gpg-agent --daemon --write-env-file "${agent_env}")
    return $?
}

# Usage example
if ! init_gpg_agent; then
    echo "Failed to initialize gpg-agent" >&2
    exit 1
fi

# Proceed with GPG operations
gpg --batch --decrypt "${encrypted_file}"