Skip to content

Commit 3cbdff5

Browse files
author
Test User
committed
fix(tasks): prevent concurrent execution and improve button UX
Phase 1 fix for concurrent execution issue: - Backend: Check for in_progress tasks before scheduling new execution - Frontend: Disable button when tasks are already running - Frontend: Show "Running..." state and updated message when execution active - Add 2 new tests for execution blocking behavior
1 parent 46947ca commit 3cbdff5

File tree

3 files changed

+88
-4
lines changed

3 files changed

+88
-4
lines changed

codeframe/ui/routers/tasks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,19 @@ async def assign_pending_tasks(
450450
message="No pending unassigned tasks to assign."
451451
)
452452

453+
# Check if execution is already in progress (Phase 1 fix for concurrent execution)
454+
in_progress_tasks = [t for t in tasks if t.status == TaskStatus.IN_PROGRESS]
455+
if in_progress_tasks:
456+
logger.info(
457+
f"⏳ Execution already in progress for project {project_id}: "
458+
f"{len(in_progress_tasks)} tasks running"
459+
)
460+
return TaskAssignmentResponse(
461+
success=True,
462+
pending_count=pending_count,
463+
message=f"Execution already in progress ({len(in_progress_tasks)} task(s) running). Please wait."
464+
)
465+
453466
# Schedule multi-agent execution in background
454467
api_key = os.environ.get("ANTHROPIC_API_KEY")
455468
if api_key:

tests/ui/test_assign_pending_tasks.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,69 @@ async def test_assign_pending_tasks_only_counts_unassigned(
239239
current_user=mock_user
240240
)
241241

242-
assert response.pending_count == 1 # Only the first task
242+
# Should not trigger because there's an in_progress task
243+
assert response.pending_count == 1
244+
assert "in progress" in response.message.lower()
245+
mock_background_tasks.add_task.assert_not_called()
246+
247+
@pytest.mark.asyncio
248+
async def test_assign_pending_tasks_blocked_when_execution_in_progress(
249+
self, mock_db, mock_user, mock_manager, mock_background_tasks
250+
):
251+
"""Test that assignment is blocked when tasks are already in progress."""
252+
from codeframe.ui.routers.tasks import assign_pending_tasks
253+
254+
# Setup: 2 pending tasks + 1 in progress
255+
mock_db.get_project_tasks.return_value = [
256+
Task(id=1, project_id=1, title="Task 1", status=TaskStatus.PENDING, assigned_to=None),
257+
Task(id=2, project_id=1, title="Task 2", status=TaskStatus.PENDING, assigned_to=None),
258+
Task(id=3, project_id=1, title="Task 3", status=TaskStatus.IN_PROGRESS, assigned_to="agent-1"),
259+
]
260+
261+
with patch("codeframe.ui.routers.tasks.manager", mock_manager), \
262+
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}):
263+
response = await assign_pending_tasks(
264+
project_id=1,
265+
background_tasks=mock_background_tasks,
266+
db=mock_db,
267+
current_user=mock_user
268+
)
269+
270+
# Should return success but NOT schedule execution
271+
assert response.success is True
272+
assert response.pending_count == 2
273+
assert "in progress" in response.message.lower()
274+
assert "1 task" in response.message.lower() # Reports 1 task running
275+
mock_background_tasks.add_task.assert_not_called()
276+
277+
@pytest.mark.asyncio
278+
async def test_assign_pending_tasks_allowed_when_no_execution_in_progress(
279+
self, mock_db, mock_user, mock_manager, mock_background_tasks
280+
):
281+
"""Test that assignment proceeds when no tasks are in progress."""
282+
from codeframe.ui.routers.tasks import assign_pending_tasks
283+
284+
# Setup: Only pending and completed tasks, no in_progress
285+
mock_db.get_project_tasks.return_value = [
286+
Task(id=1, project_id=1, title="Task 1", status=TaskStatus.PENDING, assigned_to=None),
287+
Task(id=2, project_id=1, title="Task 2", status=TaskStatus.PENDING, assigned_to=None),
288+
Task(id=3, project_id=1, title="Task 3", status=TaskStatus.COMPLETED, assigned_to="agent-1"),
289+
]
290+
291+
with patch("codeframe.ui.routers.tasks.manager", mock_manager), \
292+
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "test-key"}):
293+
response = await assign_pending_tasks(
294+
project_id=1,
295+
background_tasks=mock_background_tasks,
296+
db=mock_db,
297+
current_user=mock_user
298+
)
299+
300+
# Should schedule execution
301+
assert response.success is True
302+
assert response.pending_count == 2
303+
assert "started" in response.message.lower()
304+
mock_background_tasks.add_task.assert_called_once()
243305

244306

245307
class TestAssignPendingTasksResponseModel:

web-ui/src/components/TaskList.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ const TaskList = memo(function TaskList({ projectId }: TaskListProps) {
232232
).length;
233233
}, [projectTasks]);
234234

235+
// Check if execution is already in progress (Phase 1 fix for concurrent execution)
236+
const hasTasksInProgress = useMemo(() => {
237+
return projectTasks.some((task) => task.status === 'in_progress');
238+
}, [projectTasks]);
239+
235240
// Handler for Assign Tasks button (Issue #248 fix)
236241
const handleAssignTasks = useCallback(async () => {
237242
setIsAssigning(true);
@@ -308,16 +313,20 @@ const TaskList = memo(function TaskList({ projectId }: TaskListProps) {
308313
<div className="flex-1">
309314
<h3 className="font-medium text-foreground">Tasks Pending Assignment</h3>
310315
<p className="text-sm text-muted-foreground">
311-
{pendingUnassignedCount} task{pendingUnassignedCount !== 1 ? 's are' : ' is'} waiting to be assigned to agents
316+
{hasTasksInProgress ? (
317+
<>Execution in progress. {pendingUnassignedCount} task{pendingUnassignedCount !== 1 ? 's' : ''} waiting...</>
318+
) : (
319+
<>{pendingUnassignedCount} task{pendingUnassignedCount !== 1 ? 's are' : ' is'} waiting to be assigned to agents</>
320+
)}
312321
</p>
313322
</div>
314323
<button
315324
data-testid="assign-tasks-button"
316325
onClick={handleAssignTasks}
317-
disabled={isAssigning}
326+
disabled={isAssigning || hasTasksInProgress}
318327
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
319328
>
320-
{isAssigning ? 'Assigning...' : 'Assign Tasks'}
329+
{isAssigning ? 'Assigning...' : hasTasksInProgress ? 'Running...' : 'Assign Tasks'}
321330
</button>
322331
</div>
323332
{assignmentError && (

0 commit comments

Comments
 (0)