multica-ai/multica · warning · ErrRegistrationSessionNotFound
lark registration: session not found
Error message
lark registration: session not found
What it means
Lark registration sentinel error: the registration session id is unknown or was garbage-collected. The in-memory RegistrationService drops sessions once gcAfter passes; lookups of a missing/expired session return this error, which the handler maps to 404.
Source
Thrown at server/internal/integrations/lark/registration_service.go:698
// gcAfter when it terminates, and an expired-by-deadline session
// closes itself.
func (s *RegistrationService) gcExpiredLocked() {
now := s.cfg.Now()
s.mu.Lock()
defer s.mu.Unlock()
for id, sess := range s.sessions {
sess.mu.Lock()
drop := !sess.gcAfter.IsZero() && sess.gcAfter.Before(now)
sess.mu.Unlock()
if drop {
delete(s.sessions, id)
}
}
}
// ErrRegistrationSessionNotFound is what the service returns for
// unknown / GC'd sessions. The handler maps it to 404.
var ErrRegistrationSessionNotFound = errors.New("lark registration: session not found")
func randomSessionID() (string, error) {
buf := make([]byte, 24)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func uuidEqual(a, b pgtype.UUID) bool {
if !a.Valid || !b.Valid {
return false
}
return a.Bytes == b.Bytes
}
// botNamePreset builds the display name we pre-fill on Lark's
// PersonalAgent creation form so the installed bot readsView on GitHub (pinned to 2c0912b6ec)
Solutions
- Start the registration flow over to mint a new session id.
- If running multiple replicas, pin the registration flow to one instance (sticky routing) or back sessions with shared storage.
- Treat 404-on-resume as a normal flow outcome: the UI should offer 'start again' rather than an error page.
Example fix
// before
sess, err := svc.Resume(ctx, sessionID)
if err != nil {
return err // 500-ish leak
}
// after
sess, err := svc.Resume(ctx, sessionID)
if errors.Is(err, lark.ErrRegistrationSessionNotFound) {
http.NotFound(w, r) // client restarts the flow
return
} Defensive patterns
Strategy: try-catch
Try / catch
sess, err := regSvc.Resume(ctx, sessionID)
if err != nil {
if errors.Is(err, lark.ErrRegistrationSessionNotFound) {
// expired/GC'd/restart-wiped: restart the flow, do not retry the id
return redirectToStart(w)
}
return err
} Prevention
- Treat resume-after-404 as a normal flow branch with a 'start again' button.
- Remember sessions are in-memory: a redeploy or replica switch invalidates them all.
- Use sticky routing (or shared session storage) if the service runs multiple replicas.
When it happens
Trigger: Polling or resuming a lark registration flow with a session id that never existed, expired (GC sweep deleted it), or belonged to a previous server process — the session map is per-process memory, not durable storage.
Common situations: Service restart or redeploy mid-registration wipes the in-memory map while the user's browser holds an old session id; user walks away longer than the session lifetime then clicks continue; load-balanced replicas where the follow-up lands on a different instance than the one that created the session.
Related errors
- lark open_id is already bound to a different user
- redeemer is not a workspace member
- ErrInstallationNotFound
- Invalid desktop runtime config JSON: ${err instanceof Error
- Invalid desktop runtime config: expected a JSON object
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/3a9e6e3ec4b84773.
Report an issue: GitHub.