Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(folders): close remaining restore edge cases
Three issues caught by audit:

1. Child folder restore used isNotNull instead of timestamp matching,
   so individually-deleted child folders would be incorrectly restored.
   Now uses eq(archivedAt, folderArchivedAt) for both workflows AND
   child folders — consistent and deterministic.

2. No workspace archived check — could restore a folder into an
   archived workspace. Now checks getWorkspaceWithOwner, matching
   the existing restoreWorkflow pattern.

3. Re-restoring an already-restored folder returned an error. Now
   returns success with zero counts (idempotent).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • Loading branch information
waleedlatif1 and claude committed Apr 7, 2026
commit 22ba57d44585376d0df3247fc1ab0db6fe255516
16 changes: 11 additions & 5 deletions apps/sim/lib/workflows/orchestration/folder-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
workflowSchedule,
} from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, inArray, isNotNull, isNull } from 'drizzle-orm'
import { and, eq, inArray, isNull } from 'drizzle-orm'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { archiveWorkflowsByIdsInWorkspace } from '@/lib/workflows/lifecycle'
import type { OrchestrationErrorCode } from '@/lib/workflows/orchestration/types'
Expand Down Expand Up @@ -230,18 +230,18 @@ async function restoreFolderRecursively(
}

const archivedChildren = await tx
.select({ id: workflowFolder.id, archivedAt: workflowFolder.archivedAt })
.select({ id: workflowFolder.id })
.from(workflowFolder)
.where(
and(
eq(workflowFolder.parentId, folderId),
eq(workflowFolder.workspaceId, workspaceId),
isNotNull(workflowFolder.archivedAt)
eq(workflowFolder.archivedAt, folderArchivedAt)
)
)

for (const child of archivedChildren) {
const childStats = await restoreFolderRecursively(child.id, workspaceId, child.archivedAt!, tx)
const childStats = await restoreFolderRecursively(child.id, workspaceId, folderArchivedAt, tx)
stats.folders += childStats.folders
stats.workflows += childStats.workflows
}
Comment thread
waleedlatif1 marked this conversation as resolved.
Expand Down Expand Up @@ -283,7 +283,13 @@ export async function performRestoreFolder(
}

if (!folder.archivedAt) {
return { success: false, error: 'Folder is not archived' }
return { success: true, restoredItems: { folders: 0, workflows: 0 } }
}

const { getWorkspaceWithOwner } = await import('@/lib/workspaces/permissions/utils')
const ws = await getWorkspaceWithOwner(workspaceId)
if (!ws || ws.archivedAt) {
return { success: false, error: 'Cannot restore folder into an archived workspace' }
}

const restoredStats = await db.transaction(async (tx) => {
Expand Down
Loading