{"record":{"id":"3a9e6e3ec4b84773","repo":"multica-ai/multica","slug":"lark-registration-session-not-found","errorCode":null,"errorMessage":"lark registration: session not found","messagePattern":"lark registration: session not found","errorType":"http","errorClass":"ErrRegistrationSessionNotFound","httpStatus":404,"severity":"warning","filePath":"server/internal/integrations/lark/registration_service.go","lineNumber":698,"sourceCode":"// gcAfter when it terminates, and an expired-by-deadline session\n// closes itself.\nfunc (s *RegistrationService) gcExpiredLocked() {\n\tnow := s.cfg.Now()\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tfor id, sess := range s.sessions {\n\t\tsess.mu.Lock()\n\t\tdrop := !sess.gcAfter.IsZero() && sess.gcAfter.Before(now)\n\t\tsess.mu.Unlock()\n\t\tif drop {\n\t\t\tdelete(s.sessions, id)\n\t\t}\n\t}\n}\n\n// ErrRegistrationSessionNotFound is what the service returns for\n// unknown / GC'd sessions. The handler maps it to 404.\nvar ErrRegistrationSessionNotFound = errors.New(\"lark registration: session not found\")\n\nfunc randomSessionID() (string, error) {\n\tbuf := make([]byte, 24)\n\tif _, err := rand.Read(buf); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn base64.RawURLEncoding.EncodeToString(buf), nil\n}\n\nfunc uuidEqual(a, b pgtype.UUID) bool {\n\tif !a.Valid || !b.Valid {\n\t\treturn false\n\t}\n\treturn a.Bytes == b.Bytes\n}\n\n// botNamePreset builds the display name we pre-fill on Lark's\n// PersonalAgent creation form so the installed bot reads","sourceCodeStart":680,"sourceCodeEnd":716,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/integrations/lark/registration_service.go#L680-L716","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nsess, err := svc.Resume(ctx, sessionID)\nif err != nil {\n\treturn err // 500-ish leak\n}\n\n// after\nsess, err := svc.Resume(ctx, sessionID)\nif errors.Is(err, lark.ErrRegistrationSessionNotFound) {\n\thttp.NotFound(w, r) // client restarts the flow\n\treturn\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"sess, err := regSvc.Resume(ctx, sessionID)\nif err != nil {\n\tif errors.Is(err, lark.ErrRegistrationSessionNotFound) {\n\t\t// expired/GC'd/restart-wiped: restart the flow, do not retry the id\n\t\treturn redirectToStart(w)\n\t}\n\treturn err\n}","preventionTips":["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."],"tags":["lark","registration","session-expiry","in-memory-state","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}