Compare commits

...

2 Commits

Author SHA1 Message Date
Matt Miller
7a38045ed3 fix: localize 403 error dialog and handle both payload shapes
Address review feedback:
- Use i18n keys for dialog title and fallback message
- Read error detail from both middleware shape (response.message)
  and standard shape (response.error.message / response.error string)
- Add reportType for tracking
- Add accessRestrictedTitle and accessRestrictedMessage keys to all
  12 locale files (English text as fallback for non-English locales)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:15:19 -07:00
Matt Miller
91805b0714 fix: show clear error dialog for 403 whitelist failures
When the cloud backend returns a 403 (e.g. user not whitelisted), the
error was shown as a generic "Prompt Execution Error" with a cryptic
message. This change:

1. Adds HTTP status to PromptExecutionError so callers can distinguish
   error types
2. Catches 403 responses specifically and shows an "Access Restricted"
   dialog with the backend's actual error message

Backwards compatible: works with both the old backend message
("not authorized") and the new one ("your account is not whitelisted
for this feature" from Comfy-Org/cloud#2941).

Fixes COM-16179

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:35:48 -07:00
14 changed files with 68 additions and 14 deletions

View File

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

View File

@@ -1884,7 +1884,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

@@ -880,7 +880,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

@@ -880,7 +880,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

@@ -880,7 +880,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

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

View File

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

View File

@@ -880,7 +880,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

@@ -880,7 +880,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

@@ -880,7 +880,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

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

View File

@@ -880,7 +880,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)