fix: show clear error dialog for 403 whitelist failures (#10402)

## Summary
- When the cloud backend returns a 403 (user not whitelisted), the
frontend showed a generic "Prompt Execution Error" dialog with a cryptic
message
- Now catches 403 responses specifically and shows an "Access
Restricted" dialog with the backend's actual error message
- Adds `status` field to `PromptExecutionError` so error handlers can
distinguish HTTP status codes

## Changes
- `api.ts`: Added optional `status` to `PromptExecutionError`, pass
`res.status` from `queuePrompt`
- `app.ts`: New `else if` branch in the prompt error handler for `status
=== 403` — shows "Access Restricted" with the backend message

## Backwards compatible
- **Old backend** (`"not authorized"`): Shows "Access Restricted: not
authorized"
- **New backend**
([cloud#2941](https://github.com/Comfy-Org/cloud/pull/2941), `"your
account is not whitelisted for this feature"`): Shows "Access
Restricted: your account is not whitelisted for this feature"
- No behavior change for non-403 errors

## Related
- Backend fix: Comfy-Org/cloud#2941
- Notion: COM-16179

## Test plan
- [ ] Submit a prompt as a non-whitelisted user → should see "Access
Restricted" dialog with clear message
- [ ] Submit a prompt as a whitelisted user → no change in behavior
- [ ] Submit a prompt that fails for other reasons (missing nodes, etc.)
→ existing error handling unchanged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10402-fix-show-clear-error-dialog-for-403-whitelist-failures-32c6d73d365081eb9528d7feac4e8681)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Matt Miller <mattmiller@Matts-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Miller
2026-03-23 16:57:12 -07:00
committed by GitHub
parent 2838edbb53
commit 6a9fb4e1d5
14 changed files with 68 additions and 14 deletions

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "قد يكون السبب هو السكربت التالي",
"loadWorkflowTitle": "تم إلغاء التحميل بسبب خطأ في إعادة تحميل بيانات سير العمل",
"noStackTrace": "لا توجد تتبع للمكدس متاحة",
"promptExecutionError": "فشل تنفيذ الطلب"
"promptExecutionError": "فشل تنفيذ الطلب",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} خطأ | {count} أخطاء",

View File

@@ -1885,7 +1885,9 @@
"loadWorkflowTitle": "Loading aborted due to error reloading workflow data",
"noStackTrace": "No stacktrace available",
"extensionFileHint": "This may be due to the following script",
"promptExecutionError": "Prompt execution failed"
"promptExecutionError": "Prompt execution failed",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"apiNodesSignInDialog": {
"title": "Sign In Required to Use API Nodes",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "Esto puede deberse al siguiente script",
"loadWorkflowTitle": "La carga se interrumpió debido a un error al recargar los datos del flujo de trabajo",
"noStackTrace": "No hay seguimiento de pila disponible",
"promptExecutionError": "La ejecución del prompt falló"
"promptExecutionError": "La ejecución del prompt falló",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} ERROR | {count} ERRORES",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "این ممکن است به دلیل اسکریپت زیر باشد",
"loadWorkflowTitle": "بارگذاری به دلیل خطا در بارگذاری مجدد داده‌های workflow متوقف شد",
"noStackTrace": "هیچ stacktraceی موجود نیست",
"promptExecutionError": "اجرای prompt با شکست مواجه شد"
"promptExecutionError": "اجرای prompt با شکست مواجه شد",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} خطا",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "Cela peut être dû au script suivant",
"loadWorkflowTitle": "Chargement interrompu en raison d'une erreur de rechargement des données de workflow",
"noStackTrace": "Aucune trace de pile disponible",
"promptExecutionError": "L'exécution de l'invite a échoué"
"promptExecutionError": "L'exécution de l'invite a échoué",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} ERREUR | {count} ERREURS",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "これは次のスクリプトが原因かもしれません",
"loadWorkflowTitle": "ワークフローデータの再読み込みエラーにより、読み込みが中止されました",
"noStackTrace": "スタックトレースは利用できません",
"promptExecutionError": "プロンプトの実行に失敗しました"
"promptExecutionError": "プロンプトの実行に失敗しました",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} 件のエラー",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "다음 스크립트 때문일 수 있습니다",
"loadWorkflowTitle": "워크플로 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다",
"noStackTrace": "스택 추적을 사용할 수 없습니다",
"promptExecutionError": "프롬프트 실행 실패"
"promptExecutionError": "프롬프트 실행 실패",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count}개 오류",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "Isso pode ser devido ao seguinte script",
"loadWorkflowTitle": "Carregamento abortado devido a erro ao recarregar os dados do fluxo de trabalho",
"noStackTrace": "Nenhum stacktrace disponível",
"promptExecutionError": "Falha na execução do prompt"
"promptExecutionError": "Falha na execução do prompt",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} ERRO | {count} ERROS",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "Это может быть связано со следующим скриптом",
"loadWorkflowTitle": "Загрузка прервана из-за ошибки при перезагрузке данных рабочего процесса",
"noStackTrace": "Стек вызовов недоступен",
"promptExecutionError": "Ошибка выполнения запроса"
"promptExecutionError": "Ошибка выполнения запроса",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} ОШИБОК | {count} ОШИБКА | {count} ОШИБКИ",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "Bu, aşağıdaki komut dosyasından kaynaklanıyor olabilir",
"loadWorkflowTitle": "İş akışı verileri yeniden yüklenirken hata nedeniyle yükleme iptal edildi",
"noStackTrace": "Yığın izi mevcut değil",
"promptExecutionError": "İstem yürütmesi başarısız oldu"
"promptExecutionError": "İstem yürütmesi başarısız oldu",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} HATA | {count} HATA",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "這可能是由於以下指令碼所致",
"loadWorkflowTitle": "由於重新載入工作流程資料時發生錯誤,已中止載入",
"noStackTrace": "沒有可用的堆疊追蹤",
"promptExecutionError": "提示執行失敗"
"promptExecutionError": "提示執行失敗",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count} 個錯誤",

View File

@@ -888,7 +888,9 @@
"extensionFileHint": "这可能是由于以下脚本",
"loadWorkflowTitle": "由于重新加载工作流数据出错,加载被中止",
"noStackTrace": "无可用堆栈跟踪",
"promptExecutionError": "提示执行失败"
"promptExecutionError": "提示执行失败",
"accessRestrictedTitle": "Access Restricted",
"accessRestrictedMessage": "Your account is not authorized for this feature."
},
"errorOverlay": {
"errorCount": "{count}个错误",

View File

@@ -281,10 +281,12 @@ export interface ComfyApi extends EventTarget {
export class PromptExecutionError extends Error {
response: PromptResponse
status?: number
constructor(response: PromptResponse) {
constructor(response: PromptResponse, status?: number) {
super('Prompt execution failed')
this.response = response
this.status = status
}
override toString() {
@@ -901,7 +903,7 @@ export class ComfyApi extends EventTarget {
}
}
}
throw new PromptExecutionError(errorResponse)
throw new PromptExecutionError(errorResponse, res.status)
}
return await res.json()

View File

@@ -1647,6 +1647,34 @@ export class ComfyApp {
) {
// Re-scan the full graph instead of using the server's single-node response.
rescanAndSurfaceMissingNodes(this.rootGraph)
} else if (
error instanceof PromptExecutionError &&
error.status === 403
) {
// User is authenticated but not authorized (e.g. not whitelisted).
// Show a clear message instead of a generic error or sign-in prompt.
// The response may be middleware JSON {"message": "..."} or the
// standard {"error": {"message": "..."}} shape, so check both.
const raw =
error.response && typeof error.response === 'object'
? (error.response as Record<string, unknown>)
: {}
const rawError =
raw.error && typeof raw.error === 'object'
? (raw.error as Record<string, unknown>)
: undefined
const detail =
typeof raw.message === 'string'
? raw.message
: typeof rawError?.message === 'string'
? rawError.message
: typeof raw.error === 'string'
? raw.error
: t('errorDialog.accessRestrictedMessage')
useDialogService().showErrorDialog(new Error(detail), {
title: t('errorDialog.accessRestrictedTitle'),
reportType: 'accessRestrictedError'
})
} else if (
!useSettingStore().get('Comfy.RightSidePanel.ShowErrorsTab') ||
!(error instanceof PromptExecutionError)