Skip to content

feat(scheduler): concurrency cap on by default + fail-open fix (v0.143.0)#244

Merged
vikasprogrammer merged 1 commit into
mainfrom
feat/concurrency-cap-p1
Jul 13, 2026
Merged

feat(scheduler): concurrency cap on by default + fail-open fix (v0.143.0)#244
vikasprogrammer merged 1 commit into
mainfrom
feat/concurrency-cap-p1

Conversation

@vikasprogrammer

Copy link
Copy Markdown
Owner

Phase 1 of docs/concurrency-cap-plan.md — turn the whole-box concurrency cap on, correctly.

Why

Every live session holds a claude process (hundreds of MB). Automations/cron/tasks can burst; an unbounded burst swaps/OOMs the box. The cap already existed but was opt-in and 0 (unlimited) by default, and it fail-opened to 0 whenever tmux liveness couldn't be polled — disabling itself under exactly the load it's for.

What changed

  • Cap is ON by default, RAM-derived. derivedConcurrencyCap() = max(3, floor(totalGB / 1.5)) (2 GB → 3; 32 GB → ~21). Resolved live as env → operator Settings → derived by the new Automations.concurrencyCap() (single source of truth; the old private readonly maxConcurrent field is gone). A Settings change takes effect on the next tick — no restart.
  • Fail-open fixed. TerminalManager.aliveSessionCount() now falls back to runningSessionCount() (a DB count of running rows) when aliveNames() is null, instead of 0. The crash sweep keeps that set honest, so it's a safe proxy.
  • Tunable from the console. Settings.maxConcurrentSessions()/setMaxConcurrentSessions() (null = unset → derived; 0 = unlimited; N>0 = cap), GET/PUT /api/settings/concurrency (owner/admin, audited settings.concurrency.updated), and a Settings → Runtime defaults → "Concurrency cap" panel showing the live running count + effective cap + its source (env / operator / box default). Env-pinned installs render read-only.

Scope

Phase 1 only — the cap still governs the scheduler paths (cron / once / task). The chat/webhook admission gate + durable queue is Phase 2 (unchanged here). Per-box (cross-tenant) capping stays a documented future.

Verification

  • npm run typecheck ✓ · cd web && npm run build ✓ · npm run test:governance → 68/68 ✓
  • New scripts/concurrency-cap-test.cjs20/20 (derived math, Settings get/set/clear, resolver order incl. env precedence + blank/garbage env, DB fallback).

🤖 Generated with Claude Code

…3.0)

Phase 1 of docs/concurrency-cap-plan.md. Every live session holds a claude
process (hundreds of MB); an unbounded burst of scheduled work can OOM the box.

- The scheduler cap now defaults to a RAM-derived value (max(3, floor(GB/1.5)))
  instead of 0/unlimited. Resolved live as env → operator Settings → derived by
  the new single-source-of-truth Automations.concurrencyCap() (the static
  `maxConcurrent` field is gone), so a Settings change lands next tick, no restart.
- Fix the fail-open: aliveSessionCount() fell back to 0 when tmux liveness was
  unpollable (always on the Linux LauncherSessionBackend), silently disabling the
  cap under the exact load it's for. It now uses a DB count of running rows
  (new runningSessionCount()); the crash sweep keeps that set honest.
- Settings.maxConcurrentSessions get/set (null=unset→derived, 0=unlimited, N=cap)
  + GET/PUT /api/settings/concurrency (owner/admin, audited) + a
  Settings → Runtime defaults → "Concurrency cap" panel (live count + effective
  cap + source; env-pinned installs are read-only).
- scripts/concurrency-cap-test.cjs: 20 assertions (derived math, Settings
  semantics, resolver order, DB fallback). typecheck + web build + 68/68
  governance all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vikasprogrammer vikasprogrammer merged commit 5c8d63b into main Jul 13, 2026
1 check passed
@vikasprogrammer vikasprogrammer deleted the feat/concurrency-cap-p1 branch July 13, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant