Skip to content

Commit d629948

Browse files
arpandhakalclaude
andcommitted
fix(templates): restore all-or-nothing rollback on subtask apply failure
Per product decision: don't apply a template partially or fail silently. If any subtask fails to create, roll back the parent task and throw, so the caller gets a clear error instead of a half-populated task with silently-missing subtasks. The concurrency batching from the earlier commit is retained and is what makes this safe: it prevents the connection-pool exhaustion (P2024) that was causing the failures — and would otherwise trigger this rollback and make the task "disappear". The rollback now also covers a failed getAppliedTemplateDescription (folded inside the try), so any failure rolls back consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2673cc9 commit d629948

1 file changed

Lines changed: 18 additions & 4 deletions

File tree

src/app/api/tasks/tasksShared.service.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { SupabaseActions } from '@/utils/SupabaseActions'
1818
import APIError from '@api/core/exceptions/api'
1919
import { BaseService } from '@api/core/services/base.service'
2020
import { UserRole } from '@api/core/types/user'
21-
import { AssigneeType, Prisma, StateType, Task } from '@prisma/client'
21+
import { AssigneeType, Prisma, PrismaClient, StateType, Task } from '@prisma/client'
2222
import httpStatus from 'http-status'
2323
import z from 'zod'
2424
import { AttachmentsService } from '@api/attachments/attachments.service'
@@ -622,13 +622,27 @@ export abstract class TasksSharedService extends BaseService {
622622

623623
await this.createTask(createTaskPayload, { disableSubtaskTemplates: true, manualTimestamp })
624624
} catch (e) {
625-
// A failed subtask must never delete or abort the already-created parent task
626-
// (previously caused templated tasks to vanish). Skip it; the rest still get created.
627-
console.error('TasksSharedService#createSubtasksFromTemplate | Skipping subtask that failed to create', {
625+
// All-or-nothing: if any subtask fails to apply, roll back the parent task
626+
// rather than leaving a partially-applied template, and surface the error.
627+
const deleteTask = this.db.task.delete({ where: { id: parentId } })
628+
const deleteActivityLogs = this.db.activityLog.deleteMany({ where: { taskId: parentId } })
629+
630+
await this.db.$transaction(async (tx) => {
631+
this.setTransaction(tx as PrismaClient)
632+
await deleteTask
633+
await deleteActivityLogs
634+
this.unsetTransaction()
635+
})
636+
637+
console.error('TasksSharedService#createSubtasksFromTemplate | Rolling back task creation', {
628638
parentId,
629639
subTemplateId,
630640
e,
631641
})
642+
throw new APIError(
643+
httpStatus.INTERNAL_SERVER_ERROR,
644+
'Failed to create subtask from template, new task was not created.',
645+
)
632646
}
633647
}
634648

0 commit comments

Comments
 (0)