feat(limits): make the global session cap configurable with CODEMAN_MAX_SESSIONS

The cap of 50 concurrent sessions was a hard-coded constant, so a machine
with the memory for more (or a small box that wants fewer) had to patch the
source. CODEMAN_MAX_SESSIONS now sets it at startup. The default stays 50,
and anything that is not a positive integer (empty, 0, negative, fractional,
1e3, out of safe-integer range) falls back to it, so a typo never locks
session creation.

Every site keeps reading MAX_CONCURRENT_SESSIONS, so the session routes,
cron launches, the scheduled-run loop and the multi-user per-user cap (half
the global cap unless CODEMAN_MAX_SESSIONS_PER_USER is set) all follow the
variable. The test goes through sessionCapacityState and
sessionCapacityMessage, the check every session-creating route uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claudia[bot]andClaude Opus 5.5 committed 2026-10-21 03:46:55 +02:00
1 parent 39077281d1
commit 04eb1ed158
7 files changed
+112 -8

No files matched your search

+17 -1
View File
@@ -18,11 +18,27 @@
// Session Tracking Limits
// ============================================================================
/** Default cap on concurrent sessions when `CODEMAN_MAX_SESSIONS` is not set. */
const DEFAULT_MAX_CONCURRENT_SESSIONS = 50;
/**
* Resolve the session cap from `CODEMAN_MAX_SESSIONS` (a positive integer).
* Anything else (unset, empty, 0, negative, fractional, not a number) falls back
* to {@link DEFAULT_MAX_CONCURRENT_SESSIONS}, so a typo never locks session creation.
*/
function resolveMaxConcurrentSessions(env: NodeJS.ProcessEnv = process.env): number {
const raw = env.CODEMAN_MAX_SESSIONS?.trim();
if (!raw || !/^\d+$/.test(raw)) return DEFAULT_MAX_CONCURRENT_SESSIONS;
const n = Number(raw);
return Number.isSafeInteger(n) && n >= 1 ? n : DEFAULT_MAX_CONCURRENT_SESSIONS;
}
/**
* Maximum concurrent sessions allowed.
* Each session consumes significant resources (PTY, buffers, watchers).
* 50 unless `CODEMAN_MAX_SESSIONS` says otherwise; read once at startup.
*/
export const MAX_CONCURRENT_SESSIONS = 50;
export const MAX_CONCURRENT_SESSIONS = resolveMaxConcurrentSessions();
// ============================================================================
// SSE Client Limits
+1 -1
View File
@@ -1981,7 +1981,7 @@ export const CaseOrderSchema = z.object({
/** PUT /api/session-order — global tab order (ordered sessionIds), COD-131 */
export const SessionOrderUpdateSchema = z.object({
// Bounded defensively: ids are uuid-ish (<=100 chars) and the client pushes only
// its open-tab order (max sessions is 50) — 500 leaves ample headroom while
// its open-tab order (max sessions is 50 by default) — 500 leaves ample headroom while
// keeping a hostile/buggy client from persisting megabytes into state.json.
order: z.array(z.string().max(100)).max(500),
});