Skip to content

Commit 3243feb

Browse files
committed
fix storage write context
1 parent afef76d commit 3243feb

4 files changed

Lines changed: 51 additions & 12 deletions

File tree

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,8 +715,10 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
715715
if (cacheRead) metaEntries.push({ label: 'Cache read', value: cacheRead })
716716
if (cacheWrite) metaEntries.push({ label: 'Cache write', value: cacheWrite })
717717
if (reasoning) metaEntries.push({ label: 'Reasoning tokens', value: reasoning })
718-
const costTotal = formatCostAmount(span.cost?.total)
719-
if (costTotal) metaEntries.push({ label: 'Cost', value: costTotal })
718+
// Per-span cost is intentionally not shown: cost lives only in the usage_log
719+
// ledger (the authoritative, multiplier-inclusive run total drives the header
720+
// chip). Persisted spans are cost-stripped, so a per-span row would render on
721+
// live runs but vanish on reload — show one consistent total instead.
720722
if (span.errorType) metaEntries.push({ label: 'Error type', value: span.errorType })
721723
if (span.iterationIndex !== undefined)
722724
metaEntries.push({ label: 'Iteration', value: String(span.iterationIndex + 1) })

apps/sim/lib/logs/execution/logger.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -738,14 +738,22 @@ export class ExecutionLogger implements IExecutionLoggerService {
738738

739739
stripSpanCosts((completedExecutionData as Record<string, unknown>).traceSpans)
740740

741-
const storedExecutionData = await externalizeExecutionData(
742-
completedExecutionData as Record<string, unknown>,
743-
{
741+
// Externalization requires the execution owner (workspace_files.user_id is
742+
// NOT NULL). billingUserId comes from environment.userId and is effectively
743+
// always present for a real run; if it's somehow absent, keep data inline.
744+
let storedExecutionData = completedExecutionData as Record<string, unknown>
745+
if (billingUserId) {
746+
storedExecutionData = await externalizeExecutionData(storedExecutionData, {
744747
workspaceId: existingLog?.workspaceId ?? null,
745748
workflowId: existingLog?.workflowId ?? null,
746749
executionId,
747-
}
748-
)
750+
userId: billingUserId,
751+
})
752+
} else {
753+
execLog.warn('Skipping execution-data externalization: missing owner userId', {
754+
executionId,
755+
})
756+
}
749757
const completedExecutionLargeValueKeys = collectLargeValueReferenceKeys(storedExecutionData)
750758

751759
const updatedLog = await db.transaction(async (tx) => {

apps/sim/lib/logs/execution/trace-store.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,26 @@ export const TRACE_STORE_REF_KEY = 'traceStoreRef'
2222
*/
2323
const INLINE_MARKER_KEYS = ['hasTraceSpans', 'traceSpanCount'] as const
2424

25-
interface TraceStoreContext {
25+
/**
26+
* Read-path context. Resolves an externalized payload by storage key, authorized
27+
* via the (already-authorized) workspace — no owner needed.
28+
*/
29+
interface TraceStoreReadContext {
2630
workspaceId: string | null
2731
workflowId: string | null
2832
executionId: string
2933
}
3034

35+
/**
36+
* Write-path context. Requires the execution owner's `userId`: the externalized
37+
* object is tracked in `workspace_files`, whose `user_id` column is NOT NULL
38+
* (FK -> user.id). Requiring it here makes "a write needs an owner" a
39+
* compile-time invariant, so callers must resolve the owner before persisting.
40+
*/
41+
interface TraceStoreWriteContext extends TraceStoreReadContext {
42+
userId: string
43+
}
44+
3145
/**
3246
* Recovers the workflowId embedded in a large-value storage key
3347
* (`execution/{workspaceId}/{workflowId}/{executionId}/<file>`). Used when the
@@ -66,10 +80,14 @@ export function stripSpanCosts(spans: unknown): void {
6680
*/
6781
export async function externalizeExecutionData(
6882
executionData: Record<string, unknown>,
69-
context: TraceStoreContext
83+
context: TraceStoreWriteContext
7084
): Promise<Record<string, unknown>> {
71-
const { workspaceId, workflowId, executionId } = context
72-
if (!workspaceId || !workflowId) return executionData
85+
const { workspaceId, workflowId, executionId, userId } = context
86+
// workspaceId/workflowId build the storage key and can be null for
87+
// deleted-workflow rows. userId is type-guaranteed by TraceStoreWriteContext;
88+
// the falsy check is a defensive guard against an empty string. If any are
89+
// missing the durable write can't succeed, so keep the data inline.
90+
if (!workspaceId || !workflowId || !userId) return executionData
7391

7492
try {
7593
const json = JSON.stringify(executionData)
@@ -82,6 +100,7 @@ export async function externalizeExecutionData(
82100
workspaceId,
83101
workflowId,
84102
executionId,
103+
userId,
85104
requireDurable: true,
86105
})
87106

@@ -111,7 +130,7 @@ export async function externalizeExecutionData(
111130
*/
112131
export async function materializeExecutionData(
113132
executionData: Record<string, unknown> | null | undefined,
114-
context: TraceStoreContext
133+
context: TraceStoreReadContext
115134
): Promise<Record<string, unknown>> {
116135
if (!executionData) return {}
117136

apps/sim/scripts/backfill-trace-spans.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,20 @@ async function backfillTraceStorage(
174174
executionData.hasTraceSpans = traceSpanCount > 0
175175
executionData.traceSpanCount = traceSpanCount
176176
stripSpanCosts(executionData.traceSpans)
177+
// workspace_files.user_id (NOT NULL) needs the execution owner; legacy
178+
// rows carry it under executionData.environment.userId. Rows without an
179+
// owner can't be externalized — count them as failed and skip.
180+
const environment = executionData.environment as { userId?: string } | undefined
181+
const ownerUserId = environment?.userId
182+
if (!ownerUserId) {
183+
failed++
184+
continue
185+
}
177186
const slim = await externalizeExecutionData(executionData, {
178187
workspaceId: row.workspaceId,
179188
workflowId: row.workflowId,
180189
executionId: row.executionId,
190+
userId: ownerUserId,
181191
})
182192

183193
if (!(TRACE_STORE_REF_KEY in slim)) {

0 commit comments

Comments
 (0)