{"record":{"id":"e50113a6bc558faa","repo":"paperclipai/paperclip","slug":"too-many-active-setup-token-login-sessions","errorCode":null,"errorMessage":"Too many active setup-token login sessions.","messagePattern":"Too many active setup-token login sessions\\.","errorType":"exception","errorClass":"SetupTokenSessionError","httpStatus":429,"severity":"warning","filePath":"server/src/services/setup-token-session.ts","lineNumber":836,"sourceCode":"\n  private static decrementCount(counts: Map<string, number>, key: string): void {\n    const next = (counts.get(key) ?? 0) - 1;\n    if (next > 0) {\n      counts.set(key, next);\n    } else {\n      counts.delete(key);\n    }\n  }\n\n  /** Builds the company, owner, and adapter slot key. The reservation and the\n   *  database active-slot unique index share this identity. */\n  private static slotKey(\n    scope: Pick<SetupTokenSessionScope, \"companyId\" | \"ownerUserId\" | \"adapterType\">,\n  ): string {\n    return [scope.companyId, scope.ownerUserId, scope.adapterType].join(\"\\u0000\");\n  }\n\n  /**\n   * Reserves the capacity for one start under every enforced cap. The method is\n   * synchronous, so it runs to completion before the first `await` in\n   * {@link start}. Two concurrent starts for one slot cannot interleave inside\n   * it: the first reserves the slot, and the second reads the incremented count\n   * and fails closed with the fixed 429 cap error. The method holds the per-slot\n   * and per-company semantics; the slot is the company, the owner, and the\n   * adapter. It increments no counter on a rejection, so a rejected start\n   * reserves nothing.\n   */\n  private reserveCapacity(scope: SetupTokenSessionScope): CapReservation {\n    const slotKey = SetupTokenSessionService.slotKey(scope);\n    if ((this.reservedBySlot.get(slotKey) ?? 0) >= this.caps.perOwner) {\n      throw new SetupTokenSessionError(429, SETUP_TOKEN_CAP_EXCEEDED);\n    }\n    if ((this.reservedByCompany.get(scope.companyId) ?? 0) >= this.caps.perCompany) {\n      throw new SetupTokenSessionError(429, SETUP_TOKEN_CAP_EXCEEDED);\n    }\n    SetupTokenSessionService.incrementCount(this.reservedBySlot, slotKey);","sourceCodeStart":818,"sourceCodeEnd":854,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/setup-token-session.ts#L818-L854","documentation":"Capacity rejection from SetupTokenSessionService.start: the per-slot (per-owner) cap is enforced synchronously in reserveCapacity — a slot is the company + owner + adapter triple, and when reservedBySlot for that slot already equals caps.perOwner, the start fails closed with SetupTokenSessionError 429 SETUP_TOKEN_CAP_EXCEEDED (\"Too many active setup-token login sessions.\", server/src/services/setup-token-session.ts:799). The reservation happens before any await, so concurrent starts cannot both squeeze through; a rejected start increments nothing.","triggerScenarios":"Calling start(scope) for a company/owner/adapter combination that already has caps.perOwner active setup-token login sessions — e.g. the same user starts repeated OAuth setup-token logins for the same adapter without letting earlier sessions expire (TTL) or reach a terminal state.","commonSituations":"Retry loops hammering the login-start endpoint after a slow adapter; multiple browser tabs each starting a login; tests that start sessions without terminating them; sessions lingering because the login process never completes and the TTL is long.","solutions":["List the owner's active setup-token sessions and cancel/expire the stale ones (abort or wait for TTL) before starting a new one.","Fix client retry loops to reuse the existing session (same sessionId) instead of starting a new login on each attempt.","Wait for the TTL to reap lingering sessions if no cancel API is available.","If the workload legitimately needs more concurrent logins per owner, raise caps.perOwner in service configuration."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const active = await setupTokenSessions.listActive({ companyId, ownerUserId, adapterType });\nif (active.length >= PER_OWNER_CAP) { await setupTokenSessions.cancel(active[0].sessionId); } // free one slot before start","typeGuard":null,"tryCatchPattern":"try { return await svc.start(scope); } catch (err) { if (err instanceof SetupTokenSessionError && err.status === 429) { await cancelOldestActiveSession(scope); return await svc.start(scope); } throw err; }","preventionTips":["Reuse the existing sessionId on retry instead of starting a new login session.","Cancel abandoned login sessions promptly so per-owner slots free up before TTL expiry.","Keep only one in-flight setup-token login per owner+adapter in UI flows."],"tags":["setup-token","login-session","capacity","http-429","rate-limit"],"backgroundTag":"too-many-connections","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-08-21T17:58:32.592Z","contentChangedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}