m1k1o/neko · error

session token already exists

Error message

session token already exists

What it means

Create in server/internal/session/manager.go throws this ad-hoc error when the generated (or supplied) session token is already present in manager.tokens. Sessions and their login tokens are stored in two maps; while the session ID may be free, the token must also be unique. The manager returns (nil, "") and closes over this condition to prevent two sessions sharing one token, which would let one token authenticate to multiple sessions.

Source

Thrown at server/internal/session/manager.go:106

	totalUsers      atomic.Int32
	lastUserLeftAt  atomic.Value
}

func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile) (types.Session, string, error) {
	token, err := utils.NewUID(64)
	if err != nil {
		return nil, "", err
	}

	manager.sessionsMu.Lock()
	if _, ok := manager.sessions[id]; ok {
		manager.sessionsMu.Unlock()
		return nil, "", types.ErrSessionAlreadyExists
	}

	if _, ok := manager.tokens[token]; ok {
		manager.sessionsMu.Unlock()
		return nil, "", errors.New("session token already exists")
	}

	session := &SessionCtx{
		id:      id,
		token:   token,
		manager: manager,
		logger:  manager.logger.With().Str("session_id", id).Logger(),
		profile: profile,
	}

	manager.tokens[token] = id
	manager.sessions[id] = session
	manager.sessionsMu.Unlock()

	manager.emmiter.Emit("created", session)
	manager.save()

	return session, token, nil

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Let the manager generate a random token instead of passing a fixed one to Create.
  2. Ensure the old session is destroyed (Destroy/logout) before reusing its token.
  3. If migrating state, rebuild the tokens map consistently with the sessions map so no stale token entries remain.
  4. Handle the returned error by retrying Create with a freshly generated token.

Example fix

// before
session, token, err := manager.Create("user-42", "static-token", profile)
// after
token, err := randomToken() // e.g. crypto/rand generated
if err != nil { return err }
session, token, err := manager.Create("user-42", token, profile)
Defensive patterns

Strategy: retry

Validate before calling

manager.sessionsMu.Lock()
_, idTaken := manager.sessions[id]
_, tokTaken := manager.tokens[token]
manager.sessionsMu.Unlock()
if tokTaken {
  token = generateNewRandomToken() // regenerate before calling Create
}

Try / catch

session, tok, err := manager.Create(id, token, profile)
if err != nil && err.Error() == "session token already exists" {
  return manager.Create(id, generateNewRandomToken(), profile) // retry once with fresh token
}

Prevention

When it happens

Trigger: Calling sessionMgr.Create with a token string that an existing live session already holds (e.g. manually supplying a fixed/derived token in Create's arguments instead of letting the manager generate a random one, or after restoring/duplicating sessions without clearing the tokens map).

Common situations: Developers wiring custom token generation (e.g. deriving tokens from a user ID or a static config value) instead of the default random token; tests that create two sessions with the same hardcoded token; restart/reconnect logic that reuses a previously issued token while the old session still exists in the manager.

Related errors


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/f184d1467ebd82c9. Report an issue: GitHub.