hashicorp/terraform · error

ssh client is not connected

Error message

ssh client is not connected

What it means

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.

Solutions

  1. Ensure `Communicator.Connect()` succeeds before issuing file/upload/command operations.
  2. Check the SSH host, port, and credentials in the connection block; verify reachability with `ssh` directly.
  3. If intermittent, raise retries/timeout on the connection; the built-in reconnect only fires once.
Defensive patterns

Strategy: retry

Validate before calling

// Ensure the client is connected before opening a session:
if c.client == nil {
    if err := c.Connect(nil); err != nil {
        return nil, fmt.Errorf("ssh not connected: %w", err)
    }
}

Type guard

// sshReady reports whether the communicator can open a session right now.
func sshReady(c *Communicator) bool { return c.client != nil }

Try / catch

session, err := comm.newSession()
if err != nil && strings.Contains(err.Error(), "ssh client is not connected") {
    if cerr := comm.Connect(nil); cerr != nil { return cerr }
    session, err = comm.newSession()
}

Prevention

When it happens

Trigger: `newSession()` is called while `c.client == nil` (never connected, or connection was torn down) and the fallback `c.Connect(nil)` also fails.

Common situations: 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.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/f646c9800603d4fa. Report an issue: GitHub.

Appendix: source

Thrown at internal/communicator/ssh/communicator.go:522

		if src[len(src)-1] != '/' {
			log.Printf("[DEBUG] No trailing slash, creating the source directory name")
			return scpUploadDirProtocol(filepath.Base(src), w, r, uploadEntries)
		}
		// Trailing slash, so only upload the contents
		return uploadEntries()
	}

	cmd, err := quoteScpCommand([]string{"scp", "-rvt", dst}, c.connInfo.TargetPlatform)
	if err != nil {
		return err
	}
	return c.scpSession(cmd, scpFunc)
}

func (c *Communicator) newSession() (session *ssh.Session, err error) {
	log.Println("[DEBUG] opening new ssh session")
	if c.client == nil {
		err = errors.New("ssh client is not connected")
	} else {
		session, err = c.client.NewSession()
	}

	if err != nil {
		log.Printf("[WARN] ssh session open error: '%s', attempting reconnect", err)
		if err := c.Connect(nil); err != nil {
			return nil, err
		}

		return c.client.NewSession()
	}

	return session, nil
}

func (c *Communicator) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error {
	session, err := c.newSession()

View on GitHub (pinned to d32a084675)