wavetermdev/waveterm · error · ConnectionError

no identity files remaining

Error message

no identity files remaining

What it means

The SSH public-key auth callback (createPublicKeyCallback signer iterator) returns this ConnectionError once it has exhausted both ssh-agent signers (auth sock) and configured identity key files. golang.org/x/crypto/ssh keeps invoking the callback for each offered auth method, so this error signals 'no more keys to try' rather than a single key failure. It surfaces when every candidate key has been consumed and the server still requests public-key auth.

Source

Thrown at pkg/remote/sshclient.go:317

	authSockSigners = append(authSockSigners, authSockSignersExt...)
	authSockSignersPtr := &authSockSigners

	return func() (outSigner []ssh.Signer, outErr error) {
		defer func() {
			panicErr := panichandler.PanicHandler("sshclient:publickey-callback", recover())
			if panicErr != nil {
				outErr = panicErr
			}
		}()
		// try auth sock
		if len(*authSockSignersPtr) != 0 {
			authSockSigner := (*authSockSignersPtr)[0]
			*authSockSignersPtr = (*authSockSignersPtr)[1:]
			return []ssh.Signer{authSockSigner}, nil
		}

		if len(*identityFilesPtr) == 0 {
			return nil, ConnectionError{ConnectionDebugInfo: debugInfo, Err: fmt.Errorf("no identity files remaining")}
		}
		identityFile := (*identityFilesPtr)[0]
		blocklogger.Infof(connCtx, "[conndebug] trying keyfile %q...\n", identityFile)
		*identityFilesPtr = (*identityFilesPtr)[1:]
		privateKey, ok := existingKeys[identityFile]
		if !ok {
			log.Printf("error with existingKeys, this should never happen")
			// skip this key and try with the next
			return createDummySigner()
		}

		unencryptedPrivateKey, err := ssh.ParseRawPrivateKey(privateKey)
		if err == nil {
			signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey)
			if err == nil {
				if utilfn.SafeDeref(sshKeywords.SshAddKeysToAgent) && agentClient != nil {
					agentClient.Add(agent.AddedKey{
						PrivateKey: unencryptedPrivateKey,

View on GitHub (pinned to a4447c1563)

Solutions

  1. Verify the remote's ~/.ssh/authorized_keys contains the public key corresponding to your private key.
  2. Confirm SshIdentityFile points to a readable, valid private key (check [conndebug] log line 'trying keyfile').
  3. Start ssh-agent and add your key (ssh-add), or use an agent so signers are available.
  4. Enable password or keyboard-interactive auth so another method can succeed after keys are exhausted.
  5. If the key is passphrase-protected, make sure the passphrase prompt isn't disabled by SshBatchMode.

Example fix

// before (config)
connection: { "sshidentityfile": ["~/.ssh/nonexistent_key"] }
// after
connection: { "sshidentityfile": ["~/.ssh/id_ed25519"] } // readable key whose pubkey is on the server
Defensive patterns

Strategy: fallback

Validate before calling

// before connecting, check the key exists and is readable
if _, err := os.Stat(identityFile); err != nil {
    return fmt.Errorf("identity file %s missing/unreadable: %w", identityFile, err)
}

Try / catch

var ce ConnectionError
if err := Connect(...); err != nil {
    if errors.As(err, &ce) && strings.Contains(ce.Error(), "no identity files remaining") {
        // fall back to password / keyboard-interactive auth or fix key config
    }
}

Prevention

When it happens

Trigger: SSH connection where all auth-sock signers were offered and the last configured identity file was consumed; the server continues asking for public-key auth (key rejected/not authorized), so the callback has nothing left to return.

Common situations: Public key not in the remote's authorized_keys; wrong SshIdentityFile configured; key file unreadable (permissions) so it was skipped during preload leaving an empty list; no ssh-agent running and no keys present.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/e0ad612b7c40df1e. Report an issue: GitHub.