You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A cross-platform guide for using 1Password's SSH agent to sign Git commits inside VS Code devcontainers, with conditional socket mounting that won't break devcontainers for users who don't use 1Password.
Tested Configurations
✅ macOS Sequoia 15.2 + Docker Desktop 4.37.1 + 1Password 8.x + VS Code 1.96.x (working January 2026)
⏳ Ubuntu 24.04 + Docker Engine (untested)
✅ Windows 11 + WSL2 + Docker Desktop (working January 2026)
✅ Windows 11 + WSL2 + Docker Desktop + Cursor (working March 2026)
The Problem
When using VS Code devcontainers with 1Password's SSH agent, signed git commits fail with errors like:
error: Couldn't find key in agent?
fatal: failed to write commit object
This happens because:
1Password manages SSH keys via its own agent socket
The socket forwarding mechanism differs by host operating system
Prerequisites (All Platforms)
Note: This guide is for local devcontainers running via Docker Desktop or Docker Engine. GitHub Codespaces uses different SSH forwarding mechanisms and is not covered here.
IDE Compatibility: While this guide references VS Code, it also applies to VS Code forks that support devcontainers, including Cursor and Windsurf.
Docker Desktop on macOS runs containers inside a Linux VM. Unix sockets cannot be directly mounted from macOS into this VM. Docker Desktop provides a special "magic" socket at /run/host-services/ssh-auth.sock that forwards to whatever SSH_AUTH_SOCK is set to on your Mac.
The catch: Docker Desktop is launched by launchd, which doesn't inherit your shell environment. You must configure SSH_AUTH_SOCK globally.
Host Setup
1. Enable 1Password SSH Agent
Open 1Password → Settings → Developer
Check "Use the SSH agent"
Optionally enable "Display key names when authorizing connections"
2. Create the Socket Symlink (Recommended)
1Password's actual socket path is unwieldy. Create a symlink:
Note: This must be run after every macOS restart, or you can add it to your login items. See 1Password's documentation for making it persistent.
4. Restart Docker Desktop
Quit Docker Desktop completely and reopen it. It will now pick up the SSH_AUTH_SOCK environment variable.
5. Verify Host Setup
# Should list your 1Password SSH keys
ssh-add -l
# Should also list your keys (proves Docker can see them)
docker run --rm -it \
-v /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock \
-e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock \
alpine:latest sh -c "apk add --no-cache openssh-client && ssh-add -l"
Tip: If you use the cross-platform setup (recommended), the remoteEnv path should be /tmp/ssh-agent-stable.sock instead, since the setup-ssh-agent.sh script creates a stable symlink there.
Tip: If you use the cross-platform setup (recommended), the remoteEnv path should be /tmp/ssh-agent-stable.sock instead, since the setup-ssh-agent.sh script creates a stable symlink there.
1Password on Windows uses a Windows named pipe (\\.\pipe\openssh-ssh-agent), not a Unix socket. WSL2 and Docker containers need a Unix socket. You must bridge between them using npiperelay and socat.
Host Setup
1. Enable 1Password SSH Agent (Windows Side)
Open 1Password → Settings → Developer
Check "Use the SSH agent"
Important: Disable the Windows OpenSSH Agent service to avoid conflicts:
# Run in PowerShell as AdministratorStop-Service ssh-agent
Set-Service ssh-agent -StartupType Disabled
2. Verify Windows Side
# In PowerShell (not WSL), should list your keys
ssh-add -l
3. Install npiperelay (Windows Side)
The npiperelay tool bridges Windows named pipes to WSL2. Install it using PowerShell:
# Run in PowerShell (as Administrator recommended)# Create bin directory
mkdir $env:USERPROFILE\bin -Force
# Download and extract npiperelay
cd $env:USERPROFILE\bin
Invoke-WebRequest-Uri "https://github.com/jstarks/npiperelay/releases/download/v0.1.0/npiperelay_windows_amd64.zip"-OutFile "npiperelay.zip"Expand-Archive npiperelay.zip -DestinationPath .-Force
# Add to PATH permanently
[Environment]::SetEnvironmentVariable("PATH","$env:PATH;$env:USERPROFILE\bin","User")
# Verify installation
.\npiperelay.exe-h
Note: winget install jstarks.npiperelay and scoop install npiperelay may not work reliably. The manual download method above is recommended.
Before configuring the bridge, verify WSL2 can access the Windows binary:
# Replace YOUR_WINDOWS_USERNAME with your actual Windows username
/mnt/c/Users/YOUR_WINDOWS_USERNAME/bin/npiperelay.exe -h
You should see the npiperelay help output.
6. Configure the SSH Agent Bridge
Add the following to your ~/.bashrc or ~/.zshrc:
# 1Password SSH Agent relay from Windows# IMPORTANT: Replace YOUR_WINDOWS_USERNAME with your actual Windows usernameexport SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"if! ss -a 2>/dev/null | grep -q "$SSH_AUTH_SOCK";then
rm -f "$SSH_AUTH_SOCK"
(setsid socat UNIX-LISTEN:"$SSH_AUTH_SOCK",fork EXEC:"/mnt/c/Users/YOUR_WINDOWS_USERNAME/bin/npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &) >/dev/null 2>&1fi
Important: You must use the full path to npiperelay.exe (e.g., /mnt/c/Users/billb/bin/npiperelay.exe) because Windows executables are not in WSL2's PATH by default.
7. Verify WSL2 Setup
Open a new terminal (or run source ~/.zshrc / source ~/.bashrc), then:
# Should list your 1Password SSH keys
ssh-add -l
# Should authenticate successfully
ssh -T git@github.com
Alternative: Use systemd (if WSL has systemd enabled)
Tip: If you use the cross-platform setup (recommended), the remoteEnv path should be /tmp/ssh-agent-stable.sock instead, since the setup-ssh-agent.sh script creates a stable symlink there.
Git Configuration (All Platforms)
Inside the Devcontainer
After the container starts, configure Git for SSH signing:
# Get your public key from the agent
SIGNING_KEY=$(ssh-add -L | head -1)# Configure git
git config --global gpg.format ssh
git config --global user.signingkey "$SIGNING_KEY"
git config --global commit.gpgsign true
git config --global tag.gpgsign true
Important: Remove op-ssh-sign from .gitconfig
If you previously configured 1Password on the host, your ~/.gitconfig may contain:
[gpg "ssh"]program = "/path/to/op-ssh-sign"
Remove or comment out this section. The op-ssh-sign binary doesn't exist inside the container, and it's not needed when using agent forwarding.
Verification
After rebuilding your devcontainer, verify everything works:
# 1. Check agent has keys
ssh-add -l
# Expected: Lists your SSH key(s)# 2. Test SSH connection to GitHub
ssh -T git@github.com
# Expected: "Hi username! You've successfully authenticated..."# 1Password should prompt you to authorize# 3. Check git config
git config --get gpg.format
# Expected: ssh
git config --get user.signingkey
# Expected: Your public key# 4. Test a signed commit
git commit --allow-empty -m "test: verify SSH signing"
git log --show-signature -1
# Expected: Shows "Good signature"
Troubleshooting
"The agent has no identities"
All platforms: Make sure 1Password is unlocked
All platforms: Check that your SSH key is enabled for the agent in 1Password (Key settings → "Use for SSH agent")
macOS: Did you run launchctl setenv? Did you restart Docker Desktop afterward?
Windows: Is the Windows OpenSSH Agent service disabled? (It conflicts with 1Password)
"Error connecting to agent: Connection refused" or "communication with agent failed"
macOS: The direct socket mount doesn't work. Use /run/host-services/ssh-auth.sock instead
Windows: The npiperelay bridge may not be running. Check with ps aux | grep socat
Windows: Verify npiperelay.exe path is correct: /mnt/c/Users/YOUR_USERNAME/bin/npiperelay.exe -h
Windows: Make sure you're using the full path to npiperelay.exe in your shell config (not just npiperelay.exe)
Linux: Check socket permissions: ls -la ~/.1password/agent.sock
"Couldn't get agent socket?" when committing via the IDE UI
SSH signing works in the terminal but the IDE's commit button fails:
error: Couldn't get agent socket?
fatal: failed to write commit object
This means the IDE's Source Control panel doesn't have SSH_AUTH_SOCK set. The SCM panel runs git in a non-interactive process that doesn't source .bashrc or .zshrc.
Fix: Ensure your devcontainer.json has remoteEnv set:
And that setup-ssh-agent.sh creates the stable symlink (see Cross-Platform Setup). After making these changes, rebuild the devcontainer.
Quick workaround: Commit from the integrated terminal instead of the UI commit button — the terminal has SSH_AUTH_SOCK set correctly.
"No such file or directory" for agent socket
Rebuild the devcontainer after adding the mount configuration
macOS: Make sure you're mounting /run/host-services/ssh-auth.sock, not the 1Password socket directly
Git commit fails with "cannot run op-ssh-sign"
Your .gitconfig has the [gpg "ssh"] program setting. Remove it — it's not needed with agent forwarding and the binary doesn't exist in the container.
1Password doesn't prompt for authorization
Check that "Require approval for each new application" is enabled in 1Password SSH agent settings
Try locking and unlocking 1Password
Cross-Platform Devcontainer Setup (Recommended)
Bind mounts in devcontainer.json (type=bind) fail if the source path doesn't exist on the host. This means you can't simply list all possible socket paths — the container won't start for users who don't have 1Password configured.
The solution uses initializeCommand (which runs on the host before container creation) to detect available sockets and dynamically generate a docker-compose override file. Users without 1Password get an empty override — no mount attempted, nothing breaks.
Why remoteEnv? The postStartCommand script detects the correct socket and creates a symlink at /tmp/ssh-agent-stable.sock. The remoteEnv setting ensures the IDE's own processes (including the Source Control panel's git operations) have SSH_AUTH_SOCK set — something that shell rc files alone cannot accomplish, since the IDE doesn't source .bashrc.
Important: Add docker-compose.ssh.yml to your .gitignore — it is generated and host-specific.
.devcontainer/init-ssh.sh (runs on HOST)
#!/bin/bash# Runs on the HOST via devcontainer initializeCommand.# Detects SSH agent sockets and generates a docker-compose override# with the appropriate volume mount. If no socket is found, generates# an empty override so the container starts without SSH agent forwarding.
COMPOSE_FILE=".devcontainer/docker-compose.ssh.yml"write_compose() {
local volume_entry="$1"
cat >"$COMPOSE_FILE"<<EOF# Generated by init-ssh.sh — do not edit or commitservices: elixir: volumes: - ${volume_entry}EOF
}
write_empty() {
cat >"$COMPOSE_FILE"<< 'EOF'# Generated by init-ssh.sh — no SSH agent socket detected on hostservices: elixir: {}EOF
}
case"$(uname -s)"in
Darwin)
# macOS: Docker Desktop provides a magic socket inside the VM.
write_compose "/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock"
;;
Linux)
if [ -S"${HOME}/.ssh/agent.sock" ];then# WSL2 with npiperelay/socat bridge to Windows 1Password
write_compose "${HOME}/.ssh/agent.sock:/home/vscode/.ssh/agent.sock"elif [ -S"${HOME}/.1password/agent.sock" ];then# Native Linux with 1Password
write_compose "${HOME}/.1password/agent.sock:/home/vscode/.1password/agent.sock"else
write_empty
fi
;;
*)
write_empty
;;
esac
The setup-ssh-agent.sh script (which runs inside the container via postStartCommand) remains unchanged — it detects which socket was actually mounted and configures SSH_AUTH_SOCK and Git signing accordingly.
Why this works: initializeCommand runs on the host where it can check for real socket files. The generated compose override either includes the volume mount (socket exists) or is a no-op (socket missing). The container always starts successfully regardless of whether the user has 1Password configured.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
great write up, thank you!