{"record":{"id":"994a0aa3c5325b4c","repo":"go-gitea/gitea","slug":"invalid-server-response-response-status","errorCode":null,"errorMessage":"Invalid server response: ${response.status}","messagePattern":"Invalid server response: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web_src/js/features/repo-common.ts","lineNumber":21,"sourceCode":"import {POST} from '../modules/fetch.ts';\nimport {showErrorToast} from '../modules/toast.ts';\nimport {sleep} from '../utils.ts';\nimport RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';\nimport {createApp} from 'vue';\nimport {createTippy} from '../modules/tippy.ts';\nimport {localUserSettings} from '../modules/user-settings.ts';\nimport {registerGlobalInitFunc} from '../modules/observer.ts';\n\nasync function onDownloadArchive(e: Event) {\n  e.preventDefault();\n  // there are many places using the \"archive-link\", eg: the dropdown on the repo code page, the release list\n  const el = (e.target as HTMLElement).closest<HTMLAnchorElement>('a.archive-link[href]')!;\n  const targetLoading = el.closest('.ui.dropdown') ?? el;\n  targetLoading.classList.add('is-loading', 'loading-icon-2px');\n  try {\n    for (let tryCount = 0; ;tryCount++) {\n      const response = await POST(el.href);\n      if (!response.ok) throw new Error(`Invalid server response: ${response.status}`);\n\n      const data = await response.json();\n      if (data.complete) break;\n      await sleep(Math.min((tryCount + 1) * 750, 2000));\n    }\n    window.location.assign(el.href); // the archive is ready, start real downloading\n  } catch (e) {\n    console.error(e);\n    showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500});\n  } finally {\n    targetLoading.classList.remove('is-loading', 'loading-icon-2px');\n  }\n}\n\nexport function initRepoArchiveLinks() {\n  queryElems(document, 'a.archive-link[href]', (el) => el.addEventListener('click', onDownloadArchive));\n}\n","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/go-gitea/gitea/blob/43ace7cc8ad5fa20027b1ca5b3ab5f1134972ed5/web_src/js/features/repo-common.ts#L3-L39","documentation":"This error is thrown in Gitea's client-side archive download flow (onDownloadArchive). After the user clicks an archive link (ZIP/tar download on the code page or release list), the browser POSTs/el.href polls the server's archive-generation endpoint and expects an HTTP 2xx on every poll. Any non-ok status aborts the whole download with this message, where the number is the HTTP status code returned by the Gitea server.","triggerScenarios":"POST(el.href) returns a non-2xx status while waiting for the server to finish building the archive: 500 when archive generation fails server-side (e.g., repository is empty, git archive unsupported for the ref, worker timeout), 404 when the ref/path in the link no longer exists, or 403 when the user lacks permission or the request is rejected (e.g., CSRF/session expiry).","commonSituations":"Downloading an archive for a commit/branch that was force-pushed away or deleted mid-poll; very large repositories where server-side generation times out; mirrored repos whose objects are incomplete; expired session while the polling loop (up to 2s sleeps) is still running; reverse proxy (nginx/traefik) returning 502/504 because the archive request exceeds its timeout.","solutions":["Check the number in the message: 404 => the ref no longer exists (update the page and retry); 500 => inspect the Gitea server log for the archive-generation failure; 502/504 => raise the reverse-proxy read timeout for archive endpoints","Verify the repository can produce an archive at all: git archive works locally against the same ref, and the repo is not empty/broken","Retry the download after a hard refresh so the link href and CSRF token are current","If it is proxy-timeout related, generate the archive server-side with a longer timeout or download the repository as a git clone instead"],"exampleFix":"// before\nconst response = await POST(el.href);\nif (!response.ok) throw new Error(`Invalid server response: ${response.status}`);\n\n// after (surface the server's own error message when present)\nconst response = await POST(el.href);\nif (!response.ok) {\n  const data = await response.json().catch(() => null);\n  throw new Error(`Invalid server response: ${response.status}${data?.errorMessage ? `: ${data.errorMessage}` : ''}`);\n}","handlingStrategy":"try-catch","validationCode":"// Only start the polling flow if the archive link is well-formed\nconst el = (e.target as HTMLElement).closest<HTMLAnchorElement>('a.archive-link[href]');\nif (!el || !el.getAttribute('href')?.startsWith('/')) return; // let the browser handle it natively","typeGuard":"const isArchiveLink = (el: Element | null): el is HTMLAnchorElement =>\n  el instanceof HTMLAnchorElement && !!el.getAttribute('href');","tryCatchPattern":"try {\n  for (let tryCount = 0; ;tryCount++) {\n    const response = await POST(el.href);\n    if (!response.ok) throw new Error(`Invalid server response: ${response.status}`);\n    const data = await response.json();\n    if (data.complete) break;\n    await sleep(Math.min((tryCount + 1) * 750, 2000));\n  }\n  window.location.assign(el.href);\n} catch (e) {\n  // already the shipped pattern: console.error + showErrorToast, finally removes loading state\n  showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500});\n} finally {\n  targetLoading.classList.remove('is-loading', 'loading-icon-2px');\n}","preventionTips":["Treat any non-2xx as terminal for that attempt — do not keep polling after a 4xx; only retry transient 5xx/proxy errors","Keep the page/session fresh before long downloads; re-fetch the link if the ref may have changed (force-push)","On the server side, ensure reverse-proxy read timeouts exceed archive generation time for large repos"],"tags":["network","http","archive","download","gitea-frontend"],"backgroundTag":null,"analyzedSha":"43ace7cc8ad5fa20027b1ca5b3ab5f1134972ed5","analyzedAt":"2026-08-15T09:36:00.065Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}