Skip to content

Commit 1b3708a

Browse files
waleedlatif1claude
andcommitted
fix(processing): prevent double-billing race in LoggingSession completion
When executeWorkflowCore throws, its catch block fire-and-forgets safeCompleteWithError, then re-throws. The caller's catch block also fire-and-forgets safeCompleteWithError on the same LoggingSession. Both check this.completed (still false) before either's async DB write resolves, so both proceed to completeWorkflowExecution which uses additive SQL for billing — doubling the charged cost on every failed execution. Fix: add a synchronous `completing` flag set immediately before the async work begins. This blocks concurrent callers at the guard check. On failure, the flag is reset so the safe* fallback path (completeWithCostOnlyLog) can still attempt recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6540453 commit 1b3708a

File tree

1 file changed

+13
-4
lines changed

1 file changed

+13
-4
lines changed

apps/sim/lib/logs/execution/logging-session.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export class LoggingSession {
8989
private workflowState?: WorkflowState
9090
private isResume = false
9191
private completed = false
92+
/** Synchronous flag to prevent concurrent completion attempts (race condition guard) */
93+
private completing = false
9294
private accumulatedCost: AccumulatedCost = {
9395
total: BASE_EXECUTION_CHARGE,
9496
input: 0,
@@ -267,9 +269,10 @@ export class LoggingSession {
267269
}
268270

269271
async complete(params: SessionCompleteParams = {}): Promise<void> {
270-
if (this.completed) {
272+
if (this.completed || this.completing) {
271273
return
272274
}
275+
this.completing = true
273276

274277
const { endedAt, totalDurationMs, finalOutput, traceSpans, workflowInput, executionState } =
275278
params
@@ -341,6 +344,7 @@ export class LoggingSession {
341344
logger.debug(`[${this.requestId}] Completed logging for execution ${this.executionId}`)
342345
}
343346
} catch (error) {
347+
this.completing = false
344348
logger.error(`Failed to complete logging for execution ${this.executionId}:`, {
345349
requestId: this.requestId,
346350
workflowId: this.workflowId,
@@ -353,9 +357,10 @@ export class LoggingSession {
353357
}
354358

355359
async completeWithError(params: SessionErrorCompleteParams = {}): Promise<void> {
356-
if (this.completed) {
360+
if (this.completed || this.completing) {
357361
return
358362
}
363+
this.completing = true
359364

360365
try {
361366
const { endedAt, totalDurationMs, error, traceSpans, skipCost } = params
@@ -455,6 +460,7 @@ export class LoggingSession {
455460
)
456461
}
457462
} catch (enhancedError) {
463+
this.completing = false
458464
logger.error(`Failed to complete error logging for execution ${this.executionId}:`, {
459465
requestId: this.requestId,
460466
workflowId: this.workflowId,
@@ -467,9 +473,10 @@ export class LoggingSession {
467473
}
468474

469475
async completeWithCancellation(params: SessionCancelledParams = {}): Promise<void> {
470-
if (this.completed) {
476+
if (this.completed || this.completing) {
471477
return
472478
}
479+
this.completing = true
473480

474481
try {
475482
const { endedAt, totalDurationMs, traceSpans } = params
@@ -540,6 +547,7 @@ export class LoggingSession {
540547
)
541548
}
542549
} catch (cancelError) {
550+
this.completing = false
543551
logger.error(`Failed to complete cancelled logging for execution ${this.executionId}:`, {
544552
requestId: this.requestId,
545553
workflowId: this.workflowId,
@@ -810,9 +818,10 @@ export class LoggingSession {
810818
isError: boolean
811819
status?: 'completed' | 'failed' | 'cancelled' | 'pending'
812820
}): Promise<void> {
813-
if (this.completed) {
821+
if (this.completed || this.completing) {
814822
return
815823
}
824+
this.completing = true
816825

817826
logger.warn(
818827
`[${this.requestId || 'unknown'}] Logging completion failed for execution ${this.executionId} - attempting cost-only fallback`

0 commit comments

Comments
 (0)