nats-io/nats-server · error

unable to start session: %v

Error message

unable to start session: %v

What it means

tpm2.StartAuthSession failed when policyPCRPasswordSession tried to open a policy session (SHA-256, unbound, salted-null) with the TPM. No authorization session exists, so neither sealing nor unsealing of the JetStream key can proceed. Common causes: TPM busy/out of session slots, device I/O errors, or unsupported session/algorithm combination.

Source

Thrown at server/tpm/js_ek_tpm_windows.go:214

func policyPCRPasswordSession(rwc io.ReadWriteCloser, pcr int) (sessHandle tpmutil.Handle, policy []byte, retErr error) {
	sessHandle, _, err := tpm2.StartAuthSession(
		rwc,
		tpm2.HandleNull,  /*tpmKey*/
		tpm2.HandleNull,  /*bindKey*/
		make([]byte, 16), /*nonceCaller*/
		nil,              /*secret*/
		tpm2.SessionPolicy,
		tpm2.AlgNull,
		tpm2.AlgSHA256)
	if err != nil {
		return tpm2.HandleNull, nil, fmt.Errorf("unable to start session: %v", err)
	}
	pcrSelection := tpm2.PCRSelection{
		Hash: tpm2.AlgSHA256,
		PCRs: []int{pcr},
	}
	if err := tpm2.PolicyPCR(rwc, sessHandle, nil, pcrSelection); err != nil {
		return sessHandle, nil, fmt.Errorf("unable to bind PCRs to auth policy: %v", err)
	}
	return sessHandle, policy, nil
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Check the wrapped TPM error for TPM_RC_SESSION_HANDLES exhaustion or I/O failure
  2. Close leaked sessions or reset the TPM if session slots are exhausted
  3. Verify the TPM device is accessible and supports policy sessions with SHA-256
  4. Retry the operation after the TPM recovers

Example fix

// before: session leaks accumulate
sessCreate, _, err := tpm2.StartAuthSession(...)
// after: always flush even on later failure (already done here) and cap concurrency
sem := make(chan struct{}, 1) // serialize TPM session usage
sem <- struct{}{}
sessCreate, _, err := tpm2.StartAuthSession(...)
Defensive patterns

Strategy: retry

Validate before calling

if _, err := rwc.Write(nil); err != nil { // connection sanity probe
	return fmt.Errorf("TPM connection dead: %w", err)
}

Try / catch

sessHandle, policy, err := policyPCRPasswordSession(rwc, pcr)
if err != nil && strings.Contains(err.Error(), "unable to start session") {
	time.Sleep(250 * time.Millisecond) // transient TPM busyness
	sessHandle, policy, err = policyPCRPasswordSession(rwc, pcr)
}

Prevention

When it happens

Trigger: tpm2.StartAuthSession(...) returns error — TPM session table exhausted, transient failures, or the device handle is invalid/closed.

Common situations: Too many concurrent TPM sessions (leaked sessions from prior errors); rwc closed before this call; TPM under heavy load or in a lockout state.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/62b51a64f8c748f2. Report an issue: GitHub.