Skip to content

Commit c490747

Browse files
committed
fix(copilot): surface classified tool access errors
1 parent a85a206 commit c490747

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

apps/sim/lib/copilot/tools/handlers/access.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { authorizeWorkflowByWorkspacePermission } from '@sim/platform-authz/workflow'
2+
import { OrchestrationError } from '@/lib/core/orchestration/types'
23
import type { getWorkflowById } from '@/lib/workflows/utils'
34
import { checkWorkspaceAccess, type WorkspaceAccess } from '@/lib/workspaces/permissions/utils'
45
import { listAccessibleWorkspaceRowsForUser } from '@/lib/workspaces/utils'
@@ -19,12 +20,21 @@ export async function ensureWorkflowAccess(
1920
action,
2021
})
2122

23+
// Classified, not bare Errors: the copilot error projection passes a
24+
// classified message through to the model verbatim, while an unclassified
25+
// throw collapses into the generic "system error, please retry".
2226
if (!result.workflow) {
23-
throw new Error(`Workflow ${workflowId} not found`)
27+
throw new OrchestrationError(
28+
'not_found',
29+
`Workflow not found: ${workflowId}. Pass the workflow's canonical id (copy it from workflows/**/meta.json or the tool result that created it) — a workflow name or @-mention is not an id.`
30+
)
2431
}
2532

2633
if (!result.allowed) {
27-
throw new Error(result.message || 'Unauthorized workflow access')
34+
throw new OrchestrationError(
35+
result.status === 404 ? 'not_found' : 'forbidden',
36+
result.message || 'Unauthorized workflow access'
37+
)
2838
}
2939

3040
return { workflow: result.workflow, workspaceId: result.workflow.workspaceId }

apps/sim/lib/copilot/tools/server/files/file-folders.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
requireCopilotWorkspace,
1515
} from '@/lib/copilot/tools/server/files/file-folder-application'
1616
import { decodeVfsPathSegments } from '@/lib/copilot/vfs/path-utils'
17-
import { asOrchestrationError } from '@/lib/core/orchestration/types'
17+
import { asOrchestrationError, OrchestrationError } from '@/lib/core/orchestration/types'
1818
import {
1919
findWorkspaceFileFolderIdByPath,
2020
getWorkspaceFileFolder,
@@ -119,9 +119,11 @@ async function resolveFolderIdFromPath(
119119
label = 'Folder'
120120
): Promise<string> {
121121
const segments = decodeFileFolderPath(path)
122-
if (!segments) throw new Error(`${label} path must identify a folder under files/`)
122+
if (!segments)
123+
throw new OrchestrationError('validation', `${label} path must identify a folder under files/`)
123124
const folderId = await findWorkspaceFileFolderIdByPath(workspaceId, segments)
124-
if (!folderId) throw new Error(`${label} not found at files/${segments.join('/')}`)
125+
if (!folderId)
126+
throw new OrchestrationError('not_found', `${label} not found at files/${segments.join('/')}`)
125127
return folderId
126128
}
127129

@@ -135,7 +137,11 @@ async function resolveOptionalFolderId(
135137
const segments = decodeFileFolderPath(raw)
136138
if (!segments) return null
137139
const folderId = await findWorkspaceFileFolderIdByPath(workspaceId, segments)
138-
if (!folderId) throw new Error(`Target folder not found at files/${segments.join('/')}`)
140+
if (!folderId)
141+
throw new OrchestrationError(
142+
'not_found',
143+
`Target folder not found at files/${segments.join('/')}`
144+
)
139145
return folderId
140146
}
141147

apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
type ServerToolContext,
1818
} from '@/lib/copilot/tools/server/base-tool'
1919
import { env } from '@/lib/core/config/env'
20+
import { OrchestrationError } from '@/lib/core/orchestration/types'
2021
import { getSocketServerUrl } from '@/lib/core/utils/urls'
2122
import { MAX_PLAN_REQUIRED } from '@/lib/execution/remote-sandbox/workspace-sandboxes'
2223
import {
@@ -64,7 +65,8 @@ async function getCurrentWorkflowStateFromDb(
6465
.from(workflowTable)
6566
.where(eq(workflowTable.id, workflowId))
6667
.limit(1)
67-
if (!workflowRecord) throw new Error(`Workflow ${workflowId} not found in database`)
68+
if (!workflowRecord)
69+
throw new OrchestrationError('not_found', `Workflow ${workflowId} not found in database`)
6870
const normalized = await loadWorkflowFromNormalizedTables(workflowId)
6971
if (!normalized) throw new Error('Workflow has no normalized data')
7072

@@ -112,7 +114,16 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
112114
action: 'write',
113115
})
114116
if (!authorization.allowed) {
115-
throw new Error(authorization.message || 'Unauthorized workflow access')
117+
// Classified, not a bare Error: the copilot error projection passes a
118+
// classified message through to the model verbatim, while an
119+
// unclassified throw collapses into "system error, please retry" —
120+
// which invites blind retries of a call that can never succeed.
121+
throw new OrchestrationError(
122+
authorization.status === 404 ? 'not_found' : 'forbidden',
123+
authorization.status === 404
124+
? `Workflow not found: ${workflowId}. Pass the workflow's canonical id (copy it from workflows/**/meta.json or the tool result that created it) — a workflow name or @-mention is not an id.`
125+
: authorization.message || 'Unauthorized workflow access'
126+
)
116127
}
117128

118129
await assertWorkflowMutable(workflowId)

0 commit comments

Comments
 (0)