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
- Call ServeChannelRequests once per ssh.Client (e.g. at connection setup), not per session
- Guard the call with a sync.Once or check whether forwarding is already active before calling
- 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
- Register the forwarded-agent handler exactly once per ssh.Client
- Set up forwarding at connection establishment, not per session
- Use sync.Once or connection-scoped state to deduplicate setup
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
- cannot route to empty target host
- device already holds a registered credential
- disconnect escape sequence detected
AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02).
Data as JSON: /api/errors/f05c47a008d9e8aa.
Report an issue: GitHub.