go-gitea/gitea · error
Invalid server response: ${response.status}
Error message
Invalid server response: ${response.status} What it means
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.
Source
Thrown at web_src/js/features/repo-common.ts:21
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {sleep} from '../utils.ts';
import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
import {createApp} from 'vue';
import {createTippy} from '../modules/tippy.ts';
import {localUserSettings} from '../modules/user-settings.ts';
import {registerGlobalInitFunc} from '../modules/observer.ts';
async function onDownloadArchive(e: Event) {
e.preventDefault();
// there are many places using the "archive-link", eg: the dropdown on the repo code page, the release list
const el = (e.target as HTMLElement).closest<HTMLAnchorElement>('a.archive-link[href]')!;
const targetLoading = el.closest('.ui.dropdown') ?? el;
targetLoading.classList.add('is-loading', 'loading-icon-2px');
try {
for (let tryCount = 0; ;tryCount++) {
const response = await POST(el.href);
if (!response.ok) throw new Error(`Invalid server response: ${response.status}`);
const data = await response.json();
if (data.complete) break;
await sleep(Math.min((tryCount + 1) * 750, 2000));
}
window.location.assign(el.href); // the archive is ready, start real downloading
} catch (e) {
console.error(e);
showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500});
} finally {
targetLoading.classList.remove('is-loading', 'loading-icon-2px');
}
}
export function initRepoArchiveLinks() {
queryElems(document, 'a.archive-link[href]', (el) => el.addEventListener('click', onDownloadArchive));
}
View on GitHub (pinned to 43ace7cc8a)
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
Example fix
// before
const response = await POST(el.href);
if (!response.ok) throw new Error(`Invalid server response: ${response.status}`);
// after (surface the server's own error message when present)
const response = await POST(el.href);
if (!response.ok) {
const data = await response.json().catch(() => null);
throw new Error(`Invalid server response: ${response.status}${data?.errorMessage ? `: ${data.errorMessage}` : ''}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Only start the polling flow if the archive link is well-formed
const el = (e.target as HTMLElement).closest<HTMLAnchorElement>('a.archive-link[href]');
if (!el || !el.getAttribute('href')?.startsWith('/')) return; // let the browser handle it natively Type guard
const isArchiveLink = (el: Element | null): el is HTMLAnchorElement =>
el instanceof HTMLAnchorElement && !!el.getAttribute('href'); Try / catch
try {
for (let tryCount = 0; ;tryCount++) {
const response = await POST(el.href);
if (!response.ok) throw new Error(`Invalid server response: ${response.status}`);
const data = await response.json();
if (data.complete) break;
await sleep(Math.min((tryCount + 1) * 750, 2000));
}
window.location.assign(el.href);
} catch (e) {
// already the shipped pattern: console.error + showErrorToast, finally removes loading state
showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500});
} finally {
targetLoading.classList.remove('is-loading', 'loading-icon-2px');
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Failed to update issue title: ${resp.statusText}
- Failed to update PR target branch: ${resp.statusText}
- Failed to reload page: ${resp.statusText}
- Unable to render the PDF file
AI-assisted analysis of go-gitea/gitea@43ace7cc8a (2026-08-15).
Data as JSON: /api/errors/994a0aa3c5325b4c.
Report an issue: GitHub.