{"record":{"id":"6fc00642a767b751","repo":"jesseduffield/lazydocker","slug":"ssh-tunneled-socket-never-became-available-w","errorCode":null,"errorMessage":"ssh tunneled socket never became available: %w","messagePattern":"ssh tunneled socket never became available: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/commands/ssh/ssh.go","lineNumber":108,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create ssh tunnel tmp file: %w\", err)\n\t}\n\tlocalSocket := path.Join(socketDir, \"dockerhost.sock\")\n\n\tcmd, err := self.tunnelSSH(ctx, remoteHost, localSocket)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"tunnel docker host over ssh: %w\", err)\n\t}\n\n\t// set a reasonable timeout, then wait for the socket to dial successfully\n\t// before attempting to create a new docker client\n\tconst socketTunnelTimeout = 8 * time.Second\n\tctx, cancel := context.WithTimeout(ctx, socketTunnelTimeout)\n\tdefer cancel()\n\n\terr = self.retrySocketDial(ctx, localSocket)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ssh tunneled socket never became available: %w\", err)\n\t}\n\n\t// construct the new DOCKER_HOST url with the proper scheme\n\tnewDockerHostURL := url.URL{Scheme: \"unix\", Path: localSocket}\n\treturn &tunneledDockerHost{\n\t\tsocketPath: newDockerHostURL.String(),\n\t\tcmd:        cmd,\n\t\toSCommand:  self.oSCommand,\n\t}, nil\n}\n\n// Attempt to dial the socket until it becomes available.\n// The retry loop will continue until the parent context is canceled.\nfunc (self *SSHHandler) retrySocketDial(ctx context.Context, socketPath string) error {\n\tt := time.NewTicker(1 * time.Second)\n\tdefer t.Stop()\n\n\tfor {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/jesseduffield/lazydocker/blob/7e7aadc2071d58031bf2daafca1fbd4093efc23f/pkg/commands/ssh/ssh.go#L90-L126","documentation":"After starting `ssh -L`, createDockerHostTunnel waits up to 8 seconds (retrySocketDial ticks every second) for the local unix socket to accept a connection. This error means the tunnel process started but the socket never became dialable before the context deadline — the %w chain ends in context.DeadlineExceeded. It distinguishes 'ssh could not even start' from 'ssh started but the forward never worked'.","triggerScenarios":"ssh is running but the forward is broken or too slow to establish: authentication stalls (passphrase prompt, disabled host-key acceptance in non-interactive mode), the remote /var/run/docker.sock does not exist, sshd on the remote refuses the forward (AllowTcpForwarding no / disabled unix forwarding), or the host is unreachable so ssh retries until the 8s budget runs out.","commonSituations":"First connection to a new host (host-key verification fails because lazydocker's ssh is non-interactive); key with passphrase and no agent; rootless docker on the remote (socket at /run/user/$UID/docker.sock, not /var/run/docker.sock); slow VPN/links exceeding 8 seconds to establish.","solutions":["Connect once manually to accept the host key: `ssh user@host echo ok` (fixes 'host key verification failed' stalls).","Ensure non-interactive auth works: load keys with ssh-agent, or use a passphrase-less dedicated key via ~/.ssh/config.","Confirm the remote socket path exists at /var/run/docker.sock (`ssh host ls /var/run/docker.sock`); for rootless docker, forward manually (`ssh -L /tmp/d.sock:/run/user/1000/docker.sock host -N &`) and set DOCKER_HOST=unix:///tmp/d.sock.","On slow links, retry lazydocker — the 8s budget is fixed in code, so pre-warming the connection (manual ssh first) helps."],"exampleFix":"# before\nDOCKER_HOST=ssh://user@newhost lazydocker  # first connect: host-key prompt stalls -> timeout\n\n# after\nssh user@newhost echo ok   # accept host key once\neval $(ssh-agent) && ssh-add\nDOCKER_HOST=ssh://user@newhost lazydocker","handlingStrategy":"retry","validationCode":"// preflight the exact tunnel lazydocker will build\nprobe := exec.Command(\"ssh\", \"-o\", \"BatchMode=yes\", \"-L\", \"/tmp/probe.sock:/var/run/docker.sock\", host, \"-N\")\nif err := probe.Start(); err == nil {\n    defer probe.Process.Kill()\n} else {\n    return fmt.Errorf(\"ssh tunnel preflight failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"closer, err := sshHandler.LearnDockerHostShim()\nif err != nil && strings.Contains(err.Error(), \"never became available\") {\n    time.Sleep(2 * time.Second)\n    closer, err = sshHandler.LearnDockerHostShim() // one retry: slow first-connect\n}","preventionTips":["Connect manually to new hosts once to accept host keys before automating.","Use BatchMode-friendly auth (agent-loaded keys) so ssh never blocks on prompts.","Pre-warm slow links with a manual ssh session; lazydocker's 8s socket budget is fixed."],"tags":["docker","ssh","timeout","tunnel","authentication","network"],"backgroundTag":null,"analyzedSha":"7e7aadc2071d58031bf2daafca1fbd4093efc23f","analyzedAt":"2026-08-15T09:51:48.093Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}