{"record":{"id":"f05c47a008d9e8aa","repo":"gravitational/teleport","slug":"agent-forwarding-channel-already-open","errorCode":null,"errorMessage":"agent forwarding channel already open","messagePattern":"agent forwarding channel already open","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/sshagent/client.go","lineNumber":111,"sourceCode":"\n// ServeChannelRequests routes agent channel requests to a new agent\n// connection retrieved from the provided getter.\n//\n// This method differs from [agent.ForwardToAgent] in that each agent\n// forwarding channel is handled with a new connection to the forward\n// agent, rather than sharing a single long-lived connection.\n//\n// Specifically, this is necessary for Windows' named pipe ssh agent\n// implementation, as the named pipe connection can be disrupted after\n// signature requests. This issue may be resolved directly by the\n// [agent] library once https://github.com/golang/go/issues/61383\n// is addressed.\n//\n// The agent getter must be safe to call concurrently.\nfunc ServeChannelRequests(ctx context.Context, client *ssh.Client, getForwardAgent ClientGetter) error {\n\tchannels := client.HandleChannelOpen(channelType)\n\tif channels == nil {\n\t\treturn errors.New(\"agent forwarding channel already open\")\n\t}\n\n\tgo func() {\n\t\tfor ch := range channels {\n\t\t\tgo func() {\n\t\t\t\tforwardAgent, err := getForwardAgent()\n\t\t\t\tif err != nil {\n\t\t\t\t\tslog.ErrorContext(ctx, \"failed to connect to forwarded agent\", \"err\", err)\n\t\t\t\t\t_ = ch.Reject(ssh.ConnectionFailed, ssh.ConnectionFailed.String())\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer forwardAgent.Close()\n\n\t\t\t\tchannel, reqs, err := ch.Accept()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer channel.Close()","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/sshagent/client.go#L93-L129","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: called per session\ngo ServeChannelRequests(ctx, client, getForwardAgent)\n// after: once per client\nvar once sync.Once\n...\nonce.Do(func() { go ServeChannelRequests(ctx, client, getForwardAgent) })","handlingStrategy":"validation","validationCode":"var forwardAgentOnce sync.Once\nfunc serveForwarding(ctx context.Context, client *ssh.Client, get ClientGetter) {\n    forwardAgentOnce.Do(func() {\n        if err := ServeChannelRequests(ctx, client, get); err != nil {\n            log.DebugContext(ctx, \"agent forwarding already registered\", \"error\", err)\n        }\n    })\n}","typeGuard":"func forwardingEnabled(c *ssh.Client) bool { return c.HandleChannelOpen(\"forwarded-agent\") != nil }","tryCatchPattern":"if err := ServeChannelRequests(ctx, client, getForwardAgent); err != nil {\n    if err.Error() == \"agent forwarding channel already open\" {\n        return nil // forwarding already active\n    }\n    return trace.Wrap(err)\n}","preventionTips":["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"],"tags":["ssh","agent-forwarding","duplicate-registration"],"backgroundTag":"channel-already-open","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}