From 80f0783ae155c1f4e05c246a9a42b14bc8fa2eec Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 22 Jul 2026 11:23:48 -0700 Subject: [PATCH 1/2] improvement(logs): show Redacting status while log-persist PII masking runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Log-stage PII redaction happens at persist time and can take minutes on large payloads, during which the Logs page showed the run as Running long after execution finished. The persist path now flips the log row to 'redacting' (guarded on 'running' so a concurrent cancellation is never clobbered) right before the masking work starts — only when the logs redaction stage is actually enabled — and the terminal update overwrites it with the final status. The Logs UI renders an amber non-filterable Redacting badge (row + details sidebar via the shared STATUS_CONFIG), keeps polling the detail query during the phase, and keeps resolving live progress markers. No migration: status is a free-text column, and the contract already types it as string. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ --- .../app/workspace/[workspaceId]/logs/logs.tsx | 4 +++- .../app/workspace/[workspaceId]/logs/utils.ts | 12 +++++++++- apps/sim/lib/logs/execution/logger.ts | 22 ++++++++++++++++++- apps/sim/lib/logs/fetch-log-detail.ts | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx index 9274a26e23a..269993d45bd 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx @@ -302,7 +302,9 @@ export default function Logs() { (query: { state: { data?: WorkflowLogDetail } }) => { if (!isLive) return false const status = query.state.data?.status - return status === 'running' || status === 'pending' ? ACTIVE_RUN_DETAIL_REFRESH_MS : false + return status === 'running' || status === 'pending' || status === 'redacting' + ? ACTIVE_RUN_DETAIL_REFRESH_MS + : false }, [isLive] ) diff --git a/apps/sim/app/workspace/[workspaceId]/logs/utils.ts b/apps/sim/app/workspace/[workspaceId]/logs/utils.ts index 6a28af7d077..dc1fecf2ab3 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/utils.ts @@ -18,7 +18,14 @@ export const LOG_COLUMNS = { export const DELETED_WORKFLOW_LABEL = 'Deleted Workflow' -export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled' | 'cancelling' +export type LogStatus = + | 'error' + | 'pending' + | 'running' + | 'redacting' + | 'info' + | 'cancelled' + | 'cancelling' /** * Maps raw status string to LogStatus for display. @@ -29,6 +36,8 @@ export function getDisplayStatus(status: string | null | undefined): LogStatus { switch (status) { case 'running': return 'running' + case 'redacting': + return 'redacting' case 'pending': return 'pending' case 'cancelling': @@ -55,6 +64,7 @@ export const STATUS_CONFIG: Record< error: { variant: 'red', label: 'Error', color: 'var(--text-error)', filterable: true }, pending: { variant: 'amber', label: 'Pending', color: '#f59e0b', filterable: true }, running: { variant: 'amber', label: 'Running', color: '#f59e0b', filterable: true }, + redacting: { variant: 'amber', label: 'Redacting', color: '#f59e0b', filterable: false }, cancelling: { variant: 'amber', label: 'Cancelling...', color: '#f59e0b', filterable: false }, cancelled: { variant: 'orange', label: 'Cancelled', color: '#f97316', filterable: true }, info: { diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index 0f6c642e457..0971aa64f23 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -645,7 +645,8 @@ export class ExecutionLogger implements IExecutionLoggerService { private async applyPiiRedaction( workspaceId: string | null, payload: RedactablePayload, - storeContext: { workflowId?: string | null; executionId: string; userId?: string | null } + storeContext: { workflowId?: string | null; executionId: string; userId?: string | null }, + onRedactionStart?: () => Promise ): Promise { if (!workspaceId) return payload @@ -666,6 +667,10 @@ export class ExecutionLogger implements IExecutionLoggerService { const config = resolveEffectivePiiRedaction({ orgSettings: row.orgSettings, workspaceId }).logs if (!config.enabled) return payload + // Masking large payloads can take a while; let the caller surface the phase + // (e.g. flip the log row to 'redacting') before the slow work starts. + await onRedactionStart?.() + // The string redactor can't reach values already offloaded to large-value // storage (>8MB refs). Always hydrate → mask → re-store them under the LOGS // policy, even if the block-output stage already masked before offload: that @@ -867,6 +872,21 @@ export class ExecutionLogger implements IExecutionLoggerService { workflowId: existingLog?.workflowId ?? null, executionId, userId: actorUserId, + }, + async () => { + // Execution is done but the log payload is still being masked — surface + // that as 'redacting' so the Logs UI doesn't show a stale 'running'. + // Guarded on 'running' so a concurrent cancellation is never clobbered; + // the terminal update below overwrites with the final status either way. + await db + .update(workflowExecutionLogs) + .set({ status: 'redacting' }) + .where( + and( + eq(workflowExecutionLogs.executionId, executionId), + eq(workflowExecutionLogs.status, 'running') + ) + ) } ) diff --git a/apps/sim/lib/logs/fetch-log-detail.ts b/apps/sim/lib/logs/fetch-log-detail.ts index 5b38171d6b6..8a4b329a328 100644 --- a/apps/sim/lib/logs/fetch-log-detail.ts +++ b/apps/sim/lib/logs/fetch-log-detail.ts @@ -176,7 +176,7 @@ export async function fetchLogDetail({ ) const liveMarkers = - log.status === 'running' || log.status === 'pending' + log.status === 'running' || log.status === 'pending' || log.status === 'redacting' ? ((await getProgressMarkers(log.executionId)) ?? {}) : {} const rowMarkers = (executionData ?? {}) as ExecutionProgressMarkers From 84b57660145e91409238a8541c56c8e331e9f0a1 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 22 Jul 2026 13:29:13 -0700 Subject: [PATCH 2/2] fix(logs): never let the cosmetic redacting write abort log finalization Review finding: the status flip was awaited without failure isolation, so a transient DB error there rejected applyPiiRedaction before masking and the terminal update never ran. The write is display-only; catch and warn instead. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01A1JYstmLHk9qMGyBDqYRcJ --- apps/sim/lib/logs/execution/logger.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index 0971aa64f23..c4100005ec7 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -878,15 +878,23 @@ export class ExecutionLogger implements IExecutionLoggerService { // that as 'redacting' so the Logs UI doesn't show a stale 'running'. // Guarded on 'running' so a concurrent cancellation is never clobbered; // the terminal update below overwrites with the final status either way. - await db - .update(workflowExecutionLogs) - .set({ status: 'redacting' }) - .where( - and( - eq(workflowExecutionLogs.executionId, executionId), - eq(workflowExecutionLogs.status, 'running') + // Purely cosmetic: a failed write must never abort masking/finalization. + try { + await db + .update(workflowExecutionLogs) + .set({ status: 'redacting' }) + .where( + and( + eq(workflowExecutionLogs.executionId, executionId), + eq(workflowExecutionLogs.status, 'running') + ) ) - ) + } catch (error) { + logger.warn('Failed to set redacting status on execution log', { + executionId, + error: getErrorMessage(error), + }) + } } )