{"record":{"id":"ab8a35eea939a165","repo":"gravitational/teleport","slug":"session-chunk-already-closed","errorCode":null,"errorMessage":"session chunk already closed","messagePattern":"session chunk already closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/srv/app/session.go","lineNumber":50,"sourceCode":"\t\"github.com/gravitational/teleport\"\n\tapidefaults \"github.com/gravitational/teleport/api/defaults\"\n\t\"github.com/gravitational/teleport/api/types\"\n\t\"github.com/gravitational/teleport/lib/authz\"\n\t\"github.com/gravitational/teleport/lib/events\"\n\t\"github.com/gravitational/teleport/lib/events/recorder\"\n\t\"github.com/gravitational/teleport/lib/httplib/compress\"\n\t\"github.com/gravitational/teleport/lib/httplib/reverseproxy\"\n\trsession \"github.com/gravitational/teleport/lib/session\"\n\t\"github.com/gravitational/teleport/lib/srv\"\n\t\"github.com/gravitational/teleport/lib/srv/app/common\"\n\t\"github.com/gravitational/teleport/lib/tlsca\"\n\t\"github.com/gravitational/teleport/lib/utils\"\n)\n\n// sessionChunkCloseTimeout is the default timeout used for sessionChunk.closeTimeout\nconst sessionChunkCloseTimeout = 1 * time.Hour\n\nvar errSessionChunkAlreadyClosed = errors.New(\"session chunk already closed\")\n\n// sessionChunk holds an open request handler and stream closer for an app session.\n//\n// An app session is only bounded by the lifetime of the certificate in\n// the caller's identity, so we create sessionChunks to track and record\n// chunks of live app session activity.\n//\n// Each chunk will emit an \"app.session.chunk\" event with the chunk ID\n// corresponding to the session chunk's uploaded recording. These emitted\n// chunk IDs can be used to aggregate all session uploads tied to the\n// overarching identity SessionID.\ntype sessionChunk struct {\n\tcloseC chan struct{}\n\t// id is the session chunk's uuid, which is used as the id of its session upload.\n\tid string\n\t// appName is the name of the app this session chunk belongs to, used as\n\t// a Prometheus label on the active sessions gauge.\n\tappName string","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/srv/app/session.go#L32-L68","documentation":"errSessionChunkAlreadyClosed is an unexported error in lib/srv/app/session.go indicating an app-session chunk (the tracked, recorded unit of live app session activity) has already been closed and cannot accept new inflight work. acquire() and the release path check the inflight counter; once it is set to -1 (closed) any further acquire fails.","triggerScenarios":"acquire() is called on a sessionChunk after close has been initiated: s.inflight == -1, typically when a request races with session chunk timeout/close (sessionChunkCloseTimeout, default 1 hour) or with session termination.","commonSituations":"Long-lived app sessions whose recording chunk expired or was closed while a client keeps sending requests over the same session; load balancer replaying a request to a closing session.","solutions":["Retry the request so a new app session/chunk is created","Check whether the session chunk close timeout is too aggressive for your workload and adjust session recording settings","Look for clients holding stale session IDs and force them to re-establish the session","If it reproduces consistently, examine app service logs for close/acquire race conditions"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// before reusing a session id, verify the session chunk is still live\nif !sessionRegistry.IsOpen(sessionID) {\n  sessionID = startNewAppSession(ctx, identity)\n}","typeGuard":null,"tryCatchPattern":"resp, err := doRequestWithSession(ctx, sessionID, req)\nif errors.Is(err, trace.Wrap(errSessionChunkAlreadyClosed)) || isSessionClosed(err) {\n  sessionID = startNewAppSession(ctx, identity)\n  resp, err = doRequestWithSession(ctx, sessionID, req)\n}","preventionTips":["Treat session-closed errors by re-establishing the session, not retrying the same ID","Keep session lifetimes shorter than the chunk close timeout or refresh them","Avoid long-lived idle clients holding stale session IDs"],"tags":["teleport","app-session","session-recording","race-condition"],"backgroundTag":"session-already-closed","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}