v0.8.18: table ttls, perf improvements, egress management, desktop app improvements, tools APIs, permission groups enhancements, sailpoint integration, files keyword search by waleedlatif1 · Pull Request #7381 · simstudioai/sim · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
62d0800
fix(credential-groups): stop requiring a human subject on workflow op…
TheodoreSpeaks Aug 29, 2026
4e78409
feat(function): mount referenced files into the code sandbox (#7251)
icecrasher321 Aug 29, 2026
bc07d2d
feat(admin): move a workspace between organizations (#7254)
mzxchandra Aug 29, 2026
d7c9f51
feat(library): AI Agents for Marketing Automation: Building Agentic W…
icecrasher321 Aug 29, 2026
95d1969
fix(provenance): let a run that never started report why it failed (#…
icecrasher321 Aug 29, 2026
f607c01
feat(billing): track E2B and Daytona Function sandbox usage (#7184)
BillLeoutsakosvl346 Aug 29, 2026
029ded6
refactor(tables): add column type extension points (#7119)
j15z Aug 29, 2026
7353b7c
feat(tables): add automatic row expiration with TTL (#7071)
j15z Aug 29, 2026
674ca8d
fix(timezone): consolidate table wall-clock conversion (#7072)
j15z Aug 29, 2026
f8f2d5f
feat(tables): trigger workflows on row deletes (#7161)
j15z Aug 29, 2026
2b00d96
fix(elasticsearch): resolve Cloud IDs to the real host and correct th…
waleedlatif1 Aug 30, 2026
c53837f
improvement(integrations): refresh Slack integration page SEO copy (#…
waleedlatif1 Aug 30, 2026
4851ae3
fix(helm): bump chart appVersion to the release it ships with (#7285)
waleedlatif1 Aug 30, 2026
cd42dce
fix(charts): strip ECharts navigation sinks from .chart documents (#7…
waleedlatif1 Aug 30, 2026
553849a
fix(sandbox): spend each file ceiling once across every source (#7288)
icecrasher321 Aug 30, 2026
faec125
fix(admin): report a completed workspace move's true source and its c…
icecrasher321 Aug 30, 2026
a996881
feat(files): add workspace content search (#7289)
icecrasher321 Aug 31, 2026
ba3cf4d
fix(files): dispatch search indexing from Trigger workers (#7293)
icecrasher321 Aug 31, 2026
27d7926
feat(library): Best AI Agents for Lead Enrichment in 2026 (#7292)
icecrasher321 Aug 31, 2026
ddab4a9
fix(github): wire the block fields whose ids do not match their tool …
waleedlatif1 Aug 31, 2026
f41e27a
fix(tables): harden timezone and expiration handling (#7291)
j15z Aug 31, 2026
eefbb20
fix(tables): run workflow groups from deployments (#7240)
TheodoreSpeaks Aug 31, 2026
b19553e
feat(files): add an overwrite option to file write (#7295)
TheodoreSpeaks Aug 31, 2026
d99fe97
fix(connectors): keep rate limits out of failure breaker (#7302)
waleedlatif1 Aug 31, 2026
9b0c62f
fix(chat): scope selection clipboard chips to workspace (#7298)
BillLeoutsakosvl346 Aug 31, 2026
2a8fa38
fix(workflows): authorize paused execution reads (#7303)
waleedlatif1 Aug 31, 2026
f3fb445
fix(incidentio): align declared outputs and enum hints with the v2 AP…
waleedlatif1 Aug 31, 2026
bd49a7e
fix(okta): describe each user param the way its endpoint accepts it (…
waleedlatif1 Aug 31, 2026
158214a
fix(github): make branch protection usable and wire workflow_id to it…
waleedlatif1 Aug 31, 2026
d6ef784
feat(sso): add safe member provisioning (#7309)
waleedlatif1 Aug 31, 2026
afccb72
fix(egress): unify SSRF/egress behind one policy with named provenanc…
waleedlatif1 Aug 31, 2026
27e4632
improvement(selectors): unify server-side selector execution (#7185)
BillLeoutsakosvl346 Aug 31, 2026
cb4a84d
improvement(ui): move secret visibility below description (#7315)
waleedlatif1 Aug 31, 2026
f9c22fd
fix(auth): revoke stale email share access (#7314)
waleedlatif1 Aug 31, 2026
70783dc
improvement(settings): accelerate navigation and data loading (#7299)
waleedlatif1 Aug 31, 2026
8e9aeb9
fix(executor): stop resolved data from breaking out of generated cond…
icecrasher321 Aug 31, 2026
b64a138
fix(settings): restore SSO recovery and lifecycle test isolation (#7317)
waleedlatif1 Aug 31, 2026
d8a6b6b
improvement(desktop): harden browser agent (#7311)
waleedlatif1 Aug 31, 2026
19e4570
chore(security): add proxy rate-limit regression coverage (#7318)
waleedlatif1 Aug 31, 2026
8309ad5
chore(copilot): sync mothership contracts (#7316)
j15z Aug 31, 2026
bd75254
feat(workspace): support GIF logos (#7322)
waleedlatif1 Sep 1, 2026
bd7995e
fix(selectors): normalize Bitbucket workspace UUIDs (#7320)
BillLeoutsakosvl346 Sep 1, 2026
04f7cd2
fix(notion): resolve custom page title properties (#7323)
BillLeoutsakosvl346 Sep 1, 2026
898ecdb
chore(analytics): remove Profound request tracking (#7326)
waleedlatif1 Sep 1, 2026
f4f1850
feat(library): Sim vs. Dedicated Chatbot Builders (#7325)
icecrasher321 Sep 1, 2026
616d5e9
fix(selectors): paginate HubSpot list options (#7327)
BillLeoutsakosvl346 Sep 1, 2026
e100c5a
fix(files): bound YAML expansion and buffered reads on the file-serve…
icecrasher321 Sep 1, 2026
e5e67b3
feat(circleback): add API tools, expanded block, and knowledge base c…
waleedlatif1 Sep 1, 2026
2173a70
feat(slack): add agent sessions and streaming (#7296)
TheodoreSpeaks Sep 1, 2026
fde8cf9
fix(cron): derive async job retention cutoffs from one clock read (#7…
TheodoreSpeaks Sep 1, 2026
8430f0f
fix(tables): make the agent tool picker version-aware and enrich the …
mzxchandra Sep 1, 2026
2598f44
fix(workflows): preserve contrast on gradient tiles (#7332)
waleedlatif1 Sep 1, 2026
507663f
fix(selectors): paginate SharePoint sites and lists (#7335)
BillLeoutsakosvl346 Sep 1, 2026
dcaaf8e
feat(sailpoint): add SailPoint (IGA) integration (#6092)
mzxchandra Sep 1, 2026
1c197c1
fix(selectors): paginate BigQuery resources (#7334)
BillLeoutsakosvl346 Sep 1, 2026
f2e20aa
fix(execution): stop treating the workflow owner as a live execution …
icecrasher321 Sep 1, 2026
27734f5
fix(webflow): paginate collection item selectors (#7337)
BillLeoutsakosvl346 Sep 1, 2026
b1b5de7
improvement(integrations): add Twilio SMS AEO content (#7342)
waleedlatif1 Sep 1, 2026
beef0ea
improvement(oauth): resolve credential tokens in-process instead of h…
waleedlatif1 Sep 1, 2026
694c34b
improvement(integrations): refine Slack AEO content (#7344)
waleedlatif1 Sep 1, 2026
1404e65
fix(slack): restore assistant operation compatibility (#7339)
TheodoreSpeaks Sep 1, 2026
48f72a2
feat(copilot): let Run agent cancel workflow runs (#7151)
j15z Sep 1, 2026
b4bc9b9
fix(sailpoint): complete integration validation (#7345)
waleedlatif1 Sep 1, 2026
fd061f5
improvement(deps): harden dependency graph (#7343)
waleedlatif1 Sep 1, 2026
4941c73
feat(streaming): support nested workflow outputs (#7346)
TheodoreSpeaks Sep 1, 2026
3de31cb
feat(slack): list DMs for managed credentials (#7349)
TheodoreSpeaks Sep 1, 2026
358af42
fix(docker): allow pruned lockfile normalization (#7350)
TheodoreSpeaks Sep 1, 2026
d7b31a8
feat(permission-groups): enforce every config key server-side (#7347)
waleedlatif1 Sep 1, 2026
36a4b6a
fix(canvas): repair React Flow v12 and dependency upgrade regressions…
waleedlatif1 Sep 1, 2026
9c35e18
fix(tool-input): render every tool param through the canonical sub-bl…
icecrasher321 Sep 1, 2026
349f40a
feat(sidebar): live workflow and folder registry updates via workspac…
waleedlatif1 Sep 1, 2026
11b10fb
fix(selectors): drain Snowflake result partitions (#7357)
BillLeoutsakosvl346 Sep 1, 2026
9d8d595
fix(cloudwatch): paginate log selectors (#7360)
BillLeoutsakosvl346 Sep 1, 2026
69d7266
fix(selectors): close residual cancellation gaps (#7358)
BillLeoutsakosvl346 Sep 1, 2026
4c20ff6
improvement(audits): eliminate repeated parsing and scans (#7361)
waleedlatif1 Sep 1, 2026
2ce2bec
chore(api): remove unused and superseded API routes (#7364)
waleedlatif1 Sep 1, 2026
83e5f5f
feat(library): Sim: The Open Source Zapier Alternative for AI Agents …
icecrasher321 Sep 1, 2026
e7cfef9
fix(selectors): paginate HubSpot owner options (#7359)
BillLeoutsakosvl346 Sep 1, 2026
f59c69f
fix(confluence): normalize space identifiers (#7324)
BillLeoutsakosvl346 Sep 1, 2026
df833ef
feat(ashby): expand hiring operations and webhooks (#7341)
waleedlatif1 Sep 1, 2026
d8ecc5b
perf(tests): cut per-file import graphs in apps/sim and shard the sui…
waleedlatif1 Sep 1, 2026
9acf0f8
perf(tests): drop dead upload-context re-exports and unify the CI dum…
waleedlatif1 Sep 1, 2026
23e1663
fix(deps): preserve runtime and canvas behavior (#7366)
waleedlatif1 Sep 1, 2026
1c2f708
chore(skills): teach babysit about cubic, not just Greptile (#7372)
icecrasher321 Sep 1, 2026
c43e842
fix(slack): paginate selector resources (#7362)
BillLeoutsakosvl346 Sep 1, 2026
e5a8852
feat(file): search workspace files by regular expression (#7370)
icecrasher321 Sep 1, 2026
78780c9
fix(selectors): expose incomplete loaded catalogs (#7338)
BillLeoutsakosvl346 Sep 2, 2026
150bff4
perf(tests): trim sweep, copilot, and timer-bound suites; run three C…
waleedlatif1 Sep 2, 2026
b2b4d0e
feat(models): add Claude Fable 5.1 and align Anthropic prompt-cache m…
waleedlatif1 Sep 2, 2026
4f42c61
feat(credential-groups): add event trigger (#7379)
TheodoreSpeaks Sep 2, 2026
452e542
feat(resources): remember list preferences (#7371)
j15z Sep 2, 2026
51d2118
feat(tools): run one integration tool over v2, and fix the ones that …
icecrasher321 Sep 2, 2026
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(provenance): let a run that never started report why it failed (#…
…7281)

* fix(provenance): let a run that never started report why it failed

A copilot-run workflow that fails before reaching the engine crossed
back with no provenance, which latched the tool's registry and reduced
the result to "result unavailable". The caller was told its run failed
but not that the workflow was undeployed, or the input invalid, or the
slot unavailable — the reasons this layer produces before any block
runs, naming no secret because none had been resolved yet.

The executor attaches its execution result to every throw, so the
absence of one is proof that no block ran: output, logs and error are
all undefined and the only content is a message this layer wrote. That
is an absence, not an inability to vouch, so the crossing now carries
an exact-empty envelope. The message still passes the tool boundary's
egress projection against the same registry, so anything that registry
knows is still redacted. A run that did execute and could not vouch
hands back its incomplete envelope exactly as before, and that still
latches.

Make the attach total rather than conditional to keep that inference
sound. A block failure is already normalized on the way in, so the old
`instanceof Error` guard held in practice; what it did not give was a
guarantee covering a non-Error raised by the engine's own synchronous
work. toError is identity-preserving, so ordinary failures keep their
type.

The empty envelope moves to the registry module, which owns the
vocabulary, replacing a private copy in the logging session so one
definition states what "vouched for, naming nothing" is.

* fix(provenance): keep the post-run crossing window out of the never-started claim

Review round 1, both findings accepted.

The post-run crossing runs inside the same try as the executor call, so
when that import is what throws, the catch sees an error carrying no
execution result — the same evidence a run that never started leaves.
The previous condition read that as "nothing crossed" and vouched for
it, when in fact an execution exists and its provenance was never
imported, which is exactly the content that cannot be vouched for.

Record whether the executor returned and require both facts before
claiming the absence: not past the executor, and no result attached.
Everything else hands back whatever envelope it has, and an incomplete
one still latches.

The executor test also could not fail against the old gated attach: a
block failure is normalized on the way in, so its rejection already
arrived as an Error. Drive it through the cancellation subscribe run()
awaits before the queue instead, which is its own synchronous work and
reaches the catch untouched — the case the total attach exists for.

* fix(provenance): carry the run's result through post-execution failures

Round 2, cubic's finding accepted — and it was a distinct window, not a
restatement of round 1. The executor's post-execution work runs after
the run has produced a result but before `executeWorkflow` returns, so
a failure there reached callers with no result attached: the run threw
nothing itself, and the flag added last round could not be set yet.
Every consumer that reads a missing result as "no block ran" was wrong
in that window, this crossing included.

Fix it where the result lives rather than at each reader. The executor
attaches its own on the throws it raises; `executeWorkflow` now does the
same for failures raised after it holds one, skipping the case the
executor already recorded. Logging and trace spans get the same benefit
for free — they read the identical signal.

That makes an absent result total again, so the boolean flag goes and
the crossing reads one thing: the result from the error, or the one
already returned when the failure came later still, from the crossing
itself. Only a failure with neither can claim nothing ran. The
post-return case now describes content with the run's real envelope
rather than latching blind, which is strictly more accurate than either
prior behaviour.

* fix(provenance): normalize a post-execution failure so it can carry the result

Round 3, cubic's finding accepted. The guard added last round required
the caught value to already be an `Error`, so a non-Error raised by
post-execution work skipped the attach and was rethrown bare — the same
hole this branch closed in the executor, left open one layer up by my
own change. A Copilot run would have reported an executed workflow as
never started and vouched for content it cannot describe.

Normalize once at the top of the catch and use that value throughout,
including the rethrow, matching what the executor does. `toError`
returns an `Error` unchanged, so a custom error class keeps its
identity and every ordinary failure is untouched — the existing
identity assertion on the rejection path still holds.

Two tests: the result reaches an ordinary post-execution failure, and a
non-Error one is normalized so it can carry the result too. The second
fails against the previous guard.
  • Loading branch information
icecrasher321 authored Aug 29, 2026
commit 95d19698ca694e1748413c0d1d8ef5693e71d7e2
35 changes: 34 additions & 1 deletion apps/sim/executor/execution/engine.test.ts
15 changes: 11 additions & 4 deletions apps/sim/executor/execution/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,17 @@ export class ExecutionEngine {
metadata: this.context.metadata,
}

if (error instanceof Error) {
attachExecutionResult(error, executionResult)
}
throw error
/**
* Normalized first so the attach is total rather than conditional on the throw already
* being an `Error`. A block failure is normalized on the way in, so the old guard held in
* practice; what it did not give was a guarantee. The copilot crossing reads a missing
* result as proof that no block ran, and that inference has to hold for every throw out of
* here, including a non-`Error` raised by this file's own synchronous work. `toError`
* returns an `Error` unchanged, so ordinary failures keep their identity and their type.
*/
const thrown = toError(error)
attachExecutionResult(thrown, executionResult)
throw thrown
} finally {
this.cleanup()
}
Expand Down
12 changes: 12 additions & 0 deletions apps/sim/executor/utils/resolved-secret-trace-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,18 @@ export interface ResolvedSecretIncompletenessDiagnostics {
export const ANONYMOUS_SECRET_TRACE_REPLACEMENT = OPAQUE_RESOLVED_SECRET_REPLACEMENT
export const RESOLVED_SECRET_TRACE_CHECKPOINT_VERSION = 1

/**
* The envelope for content no secret ever reached: vouched for, naming nothing.
*
* Distinct from an incomplete envelope, which says the opposite — that something may be carried
* and cannot be named. A boundary that knows nothing was resolved should say so with this rather
* than latch, since latching is the claim that redaction is impossible. Returned fresh so no
* caller shares a value it may serialize or extend.
*/
export function emptyResolvedSecretTraceProvenance(): ResolvedSecretTraceProvenanceV1 {
return { version: 1, complete: true, entries: [] }
}

const MAX_PROVENANCE_ENTRIES = PROVENANCE_MAX_ENTRIES
const MAX_SERIALIZED_PROVENANCE_BYTES = PROVENANCE_MAX_SERIALIZED_BYTES
const MAX_TRACE_CATALOG_ENTRIES = PROVENANCE_MAX_ENTRIES
Expand Down
5 changes: 1 addition & 4 deletions apps/sim/lib/logs/execution/logging-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import type { SerializableExecutionState } from '@/executor/execution/types'
import type { BlockLog } from '@/executor/types'
import { projectResolvedSecretDiagnosticError } from '@/executor/utils/resolved-secret-content-projection'
import {
emptyResolvedSecretTraceProvenance,
isResolvedSecretTraceProvenanceV1,
RESOLVED_SECRET_TRACE_CHECKPOINT_VERSION,
type ResolvedSecretTraceProvenanceV1,
Expand Down Expand Up @@ -124,10 +125,6 @@ function getActiveBlockDisplayProvenance(

const logger = createLogger('LoggingSession')

function emptyResolvedSecretTraceProvenance(): ResolvedSecretTraceProvenanceV1 {
return { version: 1, complete: true, entries: [] }
}

type CompletionAttempt = 'complete' | 'error' | 'cancelled' | 'paused'

export interface SecretSafeDisplayContent {
Expand Down
129 changes: 129 additions & 0 deletions apps/sim/lib/workflows/application/run-workflow-from-copilot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,133 @@ describe('Copilot workflow run application commands', () => {
expect(readAttemptedExecutionId(error)).toBeUndefined()
})
})

describe('failed-run provenance crossing', () => {
function trackingLifecycle() {
const importCrossingProvenance = vi.fn().mockResolvedValue(true)
return {
importCrossingProvenance,
lifecycle: {
resolvedSecretTraceRegistry: {
exportProvenanceForValue: vi.fn(() => undefined),
beginPendingActivation: vi.fn(() => vi.fn()),
importCrossingProvenance,
},
},
}
}

async function runExpectingFailure(input: { lifecycle: unknown }) {
await expect(
runWorkflowFromCopilot.execute({
principal,
input: {
workflowId: 'workflow-1',
useDraftState: true,
lifecycle: input.lifecycle,
hasWorkflowInput: false,
useMockPayload: true,
},
})
).rejects.toThrow()
}

/**
* The executor attaches its result to every throw, so a failure without one never reached a
* block. Nothing crossed, and saying so keeps the caller's tool result — and the reason its
* run could not start — instead of reducing it to "result unavailable".
*/
it('vouches for a failure that never reached the engine', async () => {
const { importCrossingProvenance, lifecycle: tracked } = trackingLifecycle()
mocks.executeWorkflow.mockRejectedValueOnce(new Error('workflow is not deployed'))

await runExpectingFailure({ lifecycle: tracked })

expect(importCrossingProvenance).toHaveBeenCalledWith(
{ version: 1, complete: true, entries: [] },
expect.objectContaining({ thrownMessage: 'workflow is not deployed' }),
expect.objectContaining({ origin: 'copilotWorkflowMutation.failedRunCrossing' })
)
})

/**
* The post-run crossing is inside the same try, so its failure reaches the catch with no
* execution result — the same evidence a never-started run leaves. An execution exists and
* its provenance was never imported, so this must not be vouched for.
*/
it('does not vouch when the crossing threw after the run returned', async () => {
const importCrossingProvenance = vi
.fn()
.mockImplementationOnce(() => {
throw new Error('crossing import failed')
})
.mockResolvedValue(true)

await runExpectingFailure({
lifecycle: {
resolvedSecretTraceRegistry: {
exportProvenanceForValue: vi.fn(() => undefined),
beginPendingActivation: vi.fn(() => vi.fn()),
importCrossingProvenance,
},
},
})

expect(importCrossingProvenance).toHaveBeenNthCalledWith(
2,
undefined,
expect.objectContaining({ thrownMessage: 'crossing import failed' }),
expect.objectContaining({ origin: 'copilotWorkflowMutation.failedRunCrossing' })
)
})

/**
* The executor's post-execution work can throw after a run has already produced a result.
* `executeWorkflow` carries it on that throw, so this reaches the catch with a result and
* must not be claimed as never-started.
*/
it('does not vouch when post-execution work threw after the engine ran', async () => {
const { importCrossingProvenance, lifecycle: tracked } = trackingLifecycle()
const incomplete = { version: 1 as const, complete: false, entries: [] }
mocks.executeWorkflow.mockRejectedValueOnce(
Object.assign(new Error('post-execution persistence failed'), {
executionResult: {
success: true,
output: { ran: true },
executionState: { resolvedSecretTraceProvenance: incomplete },
},
})
)

await runExpectingFailure({ lifecycle: tracked })

expect(importCrossingProvenance).toHaveBeenCalledWith(
incomplete,
expect.objectContaining({ output: { ran: true } }),
expect.objectContaining({ origin: 'copilotWorkflowMutation.failedRunCrossing' })
)
})

/** A run that did execute and could not vouch still hands back its incomplete envelope. */
it('passes through an incomplete envelope from a run that did execute', async () => {
const { importCrossingProvenance, lifecycle: tracked } = trackingLifecycle()
const incomplete = { version: 1 as const, complete: false, entries: [] }
const failure = Object.assign(new Error('block failed'), {
executionResult: {
success: false,
output: { partial: true },
executionState: { resolvedSecretTraceProvenance: incomplete },
},
})
mocks.executeWorkflow.mockRejectedValueOnce(failure)

await runExpectingFailure({ lifecycle: tracked })

expect(importCrossingProvenance).toHaveBeenCalledWith(
incomplete,
expect.objectContaining({ output: { partial: true } }),
expect.objectContaining({ origin: 'copilotWorkflowMutation.failedRunCrossing' })
)
})
})
})
38 changes: 28 additions & 10 deletions apps/sim/lib/workflows/application/run-workflow-from-copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ import {
} from '@/lib/workflows/triggers/run-options'
import type { SerializableExecutionState } from '@/executor/execution/types'
import type { ExecutionResult } from '@/executor/types'
import { attachAttemptedExecutionId } from '@/executor/utils/errors'
import { attachAttemptedExecutionId, hasExecutionResult } from '@/executor/utils/errors'

const logger = createLogger('CopilotWorkflowRun')

import type { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
emptyResolvedSecretTraceProvenance,
type ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'

export interface CopilotWorkflowRunLifecycle {
billingAttribution?: BillingAttributionSnapshot
Expand Down Expand Up @@ -250,6 +253,13 @@ async function executeCopilotRun(params: {
params.executionInput
)
const completePendingActivation = registry?.beginPendingActivation()
/**
* The run's own result, once the executor returns it. The post-run crossing below is inside the
* same `try`, so its failure reaches the catch carrying nothing — and on that evidence alone it
* is indistinguishable from a run that never started. Holding the result here keeps the real
* envelope available to describe content that certainly exists.
*/
let runResult: ExecutionResult | undefined
/**
* The executor call is the first statement of this `try`, so everything caught below is
* post-dispatch by construction, while authorization, admission and provenance export all
Expand Down Expand Up @@ -302,6 +312,7 @@ async function executeCopilotRun(params: {
},
childExecutionId
)
runResult = result
if (registry) {
await registry.importCrossingProvenance(
result.executionState?.resolvedSecretTraceProvenance,
Expand All @@ -325,16 +336,23 @@ async function executeCopilotRun(params: {
* as never started and invite the duplicate this id exists to prevent.
*/
if (registry) {
const executionResult =
typeof error === 'object' &&
error !== null &&
'executionResult' in error &&
typeof error.executionResult === 'object'
? (error.executionResult as ExecutionResult)
: undefined
/**
* Either source counts as proof a run exists: the error carries the result when the run or
* its post-execution work threw, and `runResult` holds it when the failure came later still
* — from the crossing below, after the executor had already returned.
*/
const executionResult = hasExecutionResult(error) ? error.executionResult : runResult
try {
/**
* Only a failure with no result from either source can claim nothing ran, and saying so
* keeps the caller's failure reason instead of reducing the tool result to "result
* unavailable" for a message that named no secret because none had been resolved yet.
* Every other failure hands back the envelope it has, and an incomplete one still latches.
*/
await registry.importCrossingProvenance(
executionResult?.executionState?.resolvedSecretTraceProvenance,
executionResult
? executionResult.executionState?.resolvedSecretTraceProvenance
: emptyResolvedSecretTraceProvenance(),
{
output: executionResult?.output,
logs: executionResult?.logs,
Expand Down
39 changes: 39 additions & 0 deletions apps/sim/lib/workflows/executor/execute-workflow.test.ts
Loading
Loading