gravitational/teleport · error

agent forwarding channel already open

Error message

agent forwarding channel already open

What it means

ServeChannelRequests registers an SSH channel handler for the 'forwarded-agent' channel type. The golang.org/x/crypto/ssh Client.HandleChannelOpen returns nil if a handler for that channel type was already registered, and this error reports that duplicate registration. It means agent forwarding is being set up twice for the same SSH connection.

Source

Thrown at lib/sshagent/client.go:111

// ServeChannelRequests routes agent channel requests to a new agent
// connection retrieved from the provided getter.
//
// This method differs from [agent.ForwardToAgent] in that each agent
// forwarding channel is handled with a new connection to the forward
// agent, rather than sharing a single long-lived connection.
//
// Specifically, this is necessary for Windows' named pipe ssh agent
// implementation, as the named pipe connection can be disrupted after
// signature requests. This issue may be resolved directly by the
// [agent] library once https://github.com/golang/go/issues/61383
// is addressed.
//
// The agent getter must be safe to call concurrently.
func ServeChannelRequests(ctx context.Context, client *ssh.Client, getForwardAgent ClientGetter) error {
	channels := client.HandleChannelOpen(channelType)
	if channels == nil {
		return errors.New("agent forwarding channel already open")
	}

	go func() {
		for ch := range channels {
			go func() {
				forwardAgent, err := getForwardAgent()
				if err != nil {
					slog.ErrorContext(ctx, "failed to connect to forwarded agent", "err", err)
					_ = ch.Reject(ssh.ConnectionFailed, ssh.ConnectionFailed.String())
					return
				}
				defer forwardAgent.Close()

				channel, reqs, err := ch.Accept()
				if err != nil {
					return
				}
				defer channel.Close()

View on GitHub (pinned to 1283425b60)

Solutions

  1. Call ServeChannelRequests once per ssh.Client (e.g. at connection setup), not per session
  2. Guard the call with a sync.Once or check whether forwarding is already active before calling
  3. If the error occurs, treat forwarding as already available rather than failing the session

Example fix

// before: called per session
go ServeChannelRequests(ctx, client, getForwardAgent)
// after: once per client
var once sync.Once
...
once.Do(func() { go ServeChannelRequests(ctx, client, getForwardAgent) })
Defensive patterns

Strategy: validation

Validate before calling

var forwardAgentOnce sync.Once
func serveForwarding(ctx context.Context, client *ssh.Client, get ClientGetter) {
    forwardAgentOnce.Do(func() {
        if err := ServeChannelRequests(ctx, client, get); err != nil {
            log.DebugContext(ctx, "agent forwarding already registered", "error", err)
        }
    })
}

Type guard

func forwardingEnabled(c *ssh.Client) bool { return c.HandleChannelOpen("forwarded-agent") != nil }

Try / catch

if err := ServeChannelRequests(ctx, client, getForwardAgent); err != nil {
    if err.Error() == "agent forwarding channel already open" {
        return nil // forwarding already active
    }
    return trace.Wrap(err)
}

Prevention

When it happens

Trigger: Calling ServeChannelRequests on an ssh.Client that already has a forwarded-agent channel handler registered — e.g. createServerSession calling it after another path already registered forwarding for the same client connection.

Common situations: Multiple sessions multiplexed over one SSH connection each attempting to enable agent forwarding; reconnect/retry logic re-invoking ServeChannelRequests on the same client; nested code paths both calling getForwardAgent setup.

Related errors


AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02). Data as JSON: /api/errors/f05c47a008d9e8aa. Report an issue: GitHub.