Skip to content

fix(kanban): forward concurrency caps from dashboard /dispatch endpoint - #83499

Closed
rickhuizinga wants to merge 1 commit into
NousResearch:mainfrom
rickhuizinga:fix/dashboard-dispatch-per-profile-cap
Closed

fix(kanban): forward concurrency caps from dashboard /dispatch endpoint#83499
rickhuizinga wants to merge 1 commit into
NousResearch:mainfrom
rickhuizinga:fix/dashboard-dispatch-per-profile-cap

Conversation

@rickhuizinga

Copy link
Copy Markdown

Problem

The dashboard's POST /dispatch endpoint (plugins/kanban/dashboard/plugin_api.py) calls dispatch_once() without forwarding max_in_progress_per_profile, max_in_progress, default_assignee, or failure_limit from the kanban config. When these parameters are None, the enforcement logic in _dispatch_once_locked skips the cap checks entirely.

This means the dashboard bypasses per-profile and global concurrency caps that the gateway tick and CLI correctly enforce.

Impact

The Desktop app's Kanban plugin auto-nudges the dispatcher after every board write (autoNudge in apps/desktop/src/plugins/kanban/api.ts — debounced 400ms, fire-and-forget). Every patchTask, createTask, deleteTask, bulkTasks, reassignTask, and reclaimTask call triggers a POST /dispatch.

Because the dashboard endpoint doesn't forward max_in_progress_per_profile, a user with kanban.max_in_progress_per_profile: 1 who creates or unblocks multiple tasks for the same profile through the Desktop Kanban plugin sees all of them spawn simultaneously, bypassing the cap. The gateway's 60-second tick enforces the cap correctly, but the dashboard's immediate dispatch circumvents it before the tick runs.

The old Dashboard web app does not have this problem — it only calls /dispatch when the user explicitly clicks "Nudge dispatcher".

Root Cause

Three callers invoke dispatch_once():

Caller Forwards caps?
Gateway watcher (kanban_watchers.py) Yes
CLI _cmd_dispatch (kanban.py) Yes
Dashboard /dispatch (plugin_api.py) No

Fix

Read the same config keys using the same _coerce_positive_int helper already used by the CLI path (kanban.py:_cmd_dispatch), then forward all four parameters to dispatch_once(). The config-reading block is copied verbatim from the CLI path to ensure identical semantics.

Parameters now forwarded

Backward compatibility

The try/except fallback (matching the CLI path) ensures that if load_config() fails, all caps fall back to None (disabled) — identical to the current behavior. Users without these config keys set see no change.

Follow-up (not in this PR)

The config-reading logic is now duplicated across three call sites. A future refactor could extract a shared dispatch_config_from_config() helper that returns a dataclass, so all three callers use one implementation.

The dashboard POST /dispatch endpoint calls dispatch_once() without
forwarding max_in_progress_per_profile, max_in_progress,
default_assignee, or failure_limit from kanban config. This means the
dashboard bypasses per-profile and global concurrency caps entirely.

The Desktop app auto-nudges the dispatcher after every board write
(autoNudge in api.ts), so every task create/unblock/reassign triggers
a dispatch_once that ignores the caps — causing all ready tasks for a
profile to spawn simultaneously.

The fix reads the same config keys using the same _coerce_positive_int
helper already used by the CLI path (kanban.py _cmd_dispatch) and the
gateway watcher (kanban_watchers.py), then forwards all four params
to dispatch_once. The gateway and CLI already do this; the dashboard
was the only caller that didn't.
@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Aug 10, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #81382. Both patches pass configured Kanban concurrency caps into the dashboard dispatch path; #81382 is the earlier, broader, regression-tested implementation.

@rickhuizinga

Copy link
Copy Markdown
Author

Thanks for the quick triage! Closing in favor of #81382 — it's more complete (regression tests, max=8 default removal). Found this independently while debugging concurrent task spawns on my homelab; glad to see it's already being addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants