(workspaceId: string)
| 10360 | } |
| 10361 | |
| 10362 | private async cleanupReportedLeafTask(workspaceId: string): Promise<void> { |
| 10363 | assert(workspaceId.length > 0, "cleanupReportedLeafTask: workspaceId must be non-empty"); |
| 10364 | |
| 10365 | // Lineage reduction: each iteration removes exactly one completed leaf, then re-evaluates |
| 10366 | // the parent on fresh config. The structural-leaf gate in canCleanupReportedTask ensures |
| 10367 | // ancestors are only deleted after every child has been pruned. |
| 10368 | let currentWorkspaceId = workspaceId; |
| 10369 | const visited = new Set<string>(); |
| 10370 | for (let depth = 0; depth < 32; depth++) { |
| 10371 | if (visited.has(currentWorkspaceId)) { |
| 10372 | log.error("cleanupReportedLeafTask: possible parentWorkspaceId cycle", { |
| 10373 | workspaceId: currentWorkspaceId, |
| 10374 | }); |
| 10375 | return; |
| 10376 | } |
| 10377 | visited.add(currentWorkspaceId); |
| 10378 | |
| 10379 | const cleanupEligibility = await this.canCleanupReportedTask(currentWorkspaceId); |
| 10380 | if (!cleanupEligibility.ok) { |
| 10381 | return; |
| 10382 | } |
| 10383 | |
| 10384 | const removeResult = await this.workspaceService.remove(currentWorkspaceId, true); |
| 10385 | if (!removeResult.success) { |
| 10386 | log.error("Failed to auto-delete completed task workspace", { |
| 10387 | workspaceId: currentWorkspaceId, |
| 10388 | error: removeResult.error, |
| 10389 | }); |
| 10390 | return; |
| 10391 | } |
| 10392 | |
| 10393 | currentWorkspaceId = cleanupEligibility.parentWorkspaceId; |
| 10394 | } |
| 10395 | |
| 10396 | log.error("cleanupReportedLeafTask: exceeded max parent traversal depth", { |
| 10397 | workspaceId, |
| 10398 | }); |
| 10399 | } |
| 10400 | } |
no test coverage detected