gravitational/teleport · error

session chunk already closed

Error message

session chunk already closed

What it means

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.

Source

Thrown at lib/srv/app/session.go:50

	"github.com/gravitational/teleport"
	apidefaults "github.com/gravitational/teleport/api/defaults"
	"github.com/gravitational/teleport/api/types"
	"github.com/gravitational/teleport/lib/authz"
	"github.com/gravitational/teleport/lib/events"
	"github.com/gravitational/teleport/lib/events/recorder"
	"github.com/gravitational/teleport/lib/httplib/compress"
	"github.com/gravitational/teleport/lib/httplib/reverseproxy"
	rsession "github.com/gravitational/teleport/lib/session"
	"github.com/gravitational/teleport/lib/srv"
	"github.com/gravitational/teleport/lib/srv/app/common"
	"github.com/gravitational/teleport/lib/tlsca"
	"github.com/gravitational/teleport/lib/utils"
)

// sessionChunkCloseTimeout is the default timeout used for sessionChunk.closeTimeout
const sessionChunkCloseTimeout = 1 * time.Hour

var errSessionChunkAlreadyClosed = errors.New("session chunk already closed")

// sessionChunk holds an open request handler and stream closer for an app session.
//
// An app session is only bounded by the lifetime of the certificate in
// the caller's identity, so we create sessionChunks to track and record
// chunks of live app session activity.
//
// Each chunk will emit an "app.session.chunk" event with the chunk ID
// corresponding to the session chunk's uploaded recording. These emitted
// chunk IDs can be used to aggregate all session uploads tied to the
// overarching identity SessionID.
type sessionChunk struct {
	closeC chan struct{}
	// id is the session chunk's uuid, which is used as the id of its session upload.
	id string
	// appName is the name of the app this session chunk belongs to, used as
	// a Prometheus label on the active sessions gauge.
	appName string

View on GitHub (pinned to 1283425b60)

Solutions

  1. Retry the request so a new app session/chunk is created
  2. Check whether the session chunk close timeout is too aggressive for your workload and adjust session recording settings
  3. Look for clients holding stale session IDs and force them to re-establish the session
  4. If it reproduces consistently, examine app service logs for close/acquire race conditions
Defensive patterns

Strategy: retry

Validate before calling

// before reusing a session id, verify the session chunk is still live
if !sessionRegistry.IsOpen(sessionID) {
  sessionID = startNewAppSession(ctx, identity)
}

Try / catch

resp, err := doRequestWithSession(ctx, sessionID, req)
if errors.Is(err, trace.Wrap(errSessionChunkAlreadyClosed)) || isSessionClosed(err) {
  sessionID = startNewAppSession(ctx, identity)
  resp, err = doRequestWithSession(ctx, sessionID, req)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02). Data as JSON: /api/errors/ab8a35eea939a165. Report an issue: GitHub.