{"record":{"id":"f646c9800603d4fa","repo":"hashicorp/terraform","slug":"ssh-client-is-not-connected","errorCode":null,"errorMessage":"ssh client is not connected","messagePattern":"ssh client is not connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/communicator/ssh/communicator.go","lineNumber":522,"sourceCode":"\t\tif src[len(src)-1] != '/' {\n\t\t\tlog.Printf(\"[DEBUG] No trailing slash, creating the source directory name\")\n\t\t\treturn scpUploadDirProtocol(filepath.Base(src), w, r, uploadEntries)\n\t\t}\n\t\t// Trailing slash, so only upload the contents\n\t\treturn uploadEntries()\n\t}\n\n\tcmd, err := quoteScpCommand([]string{\"scp\", \"-rvt\", dst}, c.connInfo.TargetPlatform)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.scpSession(cmd, scpFunc)\n}\n\nfunc (c *Communicator) newSession() (session *ssh.Session, err error) {\n\tlog.Println(\"[DEBUG] opening new ssh session\")\n\tif c.client == nil {\n\t\terr = errors.New(\"ssh client is not connected\")\n\t} else {\n\t\tsession, err = c.client.NewSession()\n\t}\n\n\tif err != nil {\n\t\tlog.Printf(\"[WARN] ssh session open error: '%s', attempting reconnect\", err)\n\t\tif err := c.Connect(nil); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn c.client.NewSession()\n\t}\n\n\treturn session, nil\n}\n\nfunc (c *Communicator) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error {\n\tsession, err := c.newSession()","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/communicator/ssh/communicator.go#L504-L540","documentation":"Returned by the SSH `Communicator.newSession` when `c.client` is nil, i.e. a session is requested before `Connect` has established the underlying `*ssh.Client`. The function then attempts an automatic reconnect and retries, so this error only escapes to the caller if the reconnect also fails.","triggerScenarios":"`newSession()` is called while `c.client == nil` (never connected, or connection was torn down) and the fallback `c.Connect(nil)` also fails.","commonSituations":"Provisioner tries to open a session before the first `Connect`; the SSH connection was dropped and reconnect fails (network, host down, auth changed); misordered communicator lifecycle in a custom provisioner.","solutions":["Ensure `Communicator.Connect()` succeeds before issuing file/upload/command operations.","Check the SSH host, port, and credentials in the connection block; verify reachability with `ssh` directly.","If intermittent, raise retries/timeout on the connection; the built-in reconnect only fires once."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Ensure the client is connected before opening a session:\nif c.client == nil {\n    if err := c.Connect(nil); err != nil {\n        return nil, fmt.Errorf(\"ssh not connected: %w\", err)\n    }\n}","typeGuard":"// sshReady reports whether the communicator can open a session right now.\nfunc sshReady(c *Communicator) bool { return c.client != nil }","tryCatchPattern":"session, err := comm.newSession()\nif err != nil && strings.Contains(err.Error(), \"ssh client is not connected\") {\n    if cerr := comm.Connect(nil); cerr != nil { return cerr }\n    session, err = comm.newSession()\n}","preventionTips":["Call Connect explicitly and check its error before any upload/command.","Verify SSH host/port/credentials in the connection block before provisioning.","Build reconnect-with-backoff around flaky hosts rather than relying on the single built-in retry."],"tags":["ssh","communicator","connection","provisioner","network"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}