{"record":{"id":"ab27d445ea78a554","repo":"gravitational/teleport","slug":"a-tncon-session-is-already-active","errorCode":null,"errorMessage":"a tncon session is already active","messagePattern":"a tncon session is already active","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/client/tncon/tncon.go","lineNumber":137,"sourceCode":"}\n\n// IsRunning determines if a tncon session is currently active.\nfunc IsRunning() bool {\n\trunningMutex.Lock()\n\tdefer runningMutex.Unlock()\n\n\treturn running\n}\n\n// Start begins a new tncon session, capturing raw input events and emitting\n// them as events. Only one session may be active at a time, but sessions can\n// be stopped\nfunc Start() error {\n\trunningMutex.Lock()\n\tdefer runningMutex.Unlock()\n\n\tif running {\n\t\treturn fmt.Errorf(\"a tncon session is already active\")\n\t}\n\n\trunning = true\n\trunningQuitHandle = C.CreateEventA(nil, C.TRUE, C.FALSE, nil)\n\n\t// Adding a buffer increases the speed of reads by a great amount,\n\t// since waiting on channel sends is the main chokepoint. Without\n\t// a sufficient buffer, the individual keystrokes won't be transmitted\n\t// quickly enough for them to be grouped as a VT sequence by Windows.\n\tsequenceBuffer = newBufferedChannelPipe(sequenceBufferSize)\n\n\tgo readInputContinuous(runningQuitHandle)\n\n\treturn nil\n}\n\n// Stop sets the stop event, requesting that the input reader quits. Subscriber\n// channels will close shortly after calling, and the subscriber list will be","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/client/tncon/tncon.go#L119-L155","documentation":"tncon supports only one active console-input capture session per process. Start() checks a package-level `running` flag under a mutex and refuses to start a second session while one is active, returning this plain error. The running session must be stopped (Stop() / terminal Close()) before a new one can begin.","triggerScenarios":"Calling tncon.Start() (directly or via Terminal.InitRaw on Windows) while a previous session is still running: calling InitRaw on two Terminal instances without closing the first, forgetting Terminal.Close(), or a prior session whose reader goroutine has not yet observed the quit event.","commonSituations":"Opening a second interactive SSH session in the same process without closing the first; a prior session crashed or was abandoned without Terminal.Close(), leaving `running` true; rapid reconnect logic that calls Start again before Stop's async cleanup (readInputContinuous) finishes and resets the flag.","solutions":["Ensure every Terminal that called InitRaw is closed (Terminal.Close()) before starting a new one.","Check tncon.IsRunning() before calling Start and skip or wait when a session is already active.","If the previous session is being torn down, wait for it to finish (Stop() then poll IsRunning() until false) before restarting.","Track Terminal instances in your application so lifecycle is explicit: one active raw terminal per process at a time."],"exampleFix":"// before\nerr := tncon.Start()\n\n// after\nif tncon.IsRunning() {\n    tncon.Stop()\n    for tncon.IsRunning() {\n        time.Sleep(10 * time.Millisecond)\n    }\n}\nerr := tncon.Start()","handlingStrategy":"try-catch","validationCode":"if tncon.IsRunning() {\n    return errors.New(\"cannot start: a tncon session is already active; close the existing Terminal first\")\n}","typeGuard":"func canStartTncon() bool {\n    return !tncon.IsRunning()\n}","tryCatchPattern":"err := t.InitRaw(true)\nif err != nil && strings.Contains(err.Error(), \"a tncon session is already active\") {\n    tncon.Stop()\n    for tncon.IsRunning() {\n        time.Sleep(10 * time.Millisecond)\n    }\n    err = t.InitRaw(true)\n}\nif err != nil {\n    return trace.Wrap(err)\n}","preventionTips":["Always pair Terminal.InitRaw with Terminal.Close (use defer).","Maintain one active raw terminal per process; track instances explicitly.","Use tncon.IsRunning() as a guard before Start.","After Stop(), allow the async reader cleanup to complete (poll IsRunning()) before restarting."],"tags":["windows","terminal","singleton","concurrency","lifecycle"],"backgroundTag":"session-already-active","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}