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.
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.
Appendix: 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)