From 6574dba9028bd19d4ea3251517c1aba9e8a56563 Mon Sep 17 00:00:00 2001 From: "steven.guiheux" Date: Tue, 19 May 2026 21:09:44 +0200 Subject: [PATCH] fix(ui): handle non-JSON error responses in form-fetch-action (#12635) ### Problem When a user clicks the merge button on a pull request and their quota is exceeded, the UI displays ( cf screenshot ): > Network error SyntaxError: Unexpected token 'Q', "Quota exceeded." is not valid JSON ### Fix Read the response body as text first with `resp.text()`, then attempt `JSON.parse()`. If parsing succeeds, use the existing `errorMessage` logic. If it fails, display the raw text directly in the error toast. This is the same approach already used by Dropzone for attachment uploads, where the `error` event handler passes the response body directly to `showErrorToast`. ( cf screenshot ) ### Tests for JavaScript changes The function is not exported, I cannot create a unit test. Do you want me to export all the logic in an exported function ? ### Release notes - [X] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12635 Reviewed-by: Mathieu Fenniak --- web_src/js/features/common-global.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index fe644e099e..a5726e94e9 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -94,13 +94,19 @@ async function fetchActionDoRequest(actionElem, url, opt) { } return; } else if (resp.status >= 400 && resp.status < 500) { - const data = await resp.json(); - // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error" - // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond. - if (data.errorMessage) { - showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'}); - } else { - showErrorToast(`server error: ${resp.status}`); + const text = await resp.text(); + try { + const data = JSON.parse(text); + // the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error" + // but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond. + if (data.errorMessage) { + showErrorToast(data.errorMessage, {useHtmlBody: data.renderFormat === 'html'}); + } else { + showErrorToast(`server error: ${resp.status}`); + } + } catch { + // if the response is not valid JSON (e.g. plain text "Quota exceeded."), show it directly + showErrorToast(text.trim() || `server error: ${resp.status}`); } } else { showErrorToast(`server error: ${resp.status}`);