actualbudget/actual · error · PostError
text (raw server response body)
Error message
text (raw server response body)
What it means
PostError whose message is the raw, non-JSON response body, thrown for any non-200 response that is not a 500, not JSON, and has no tunnel error header. The body text (HTML error page, plain-text error, proxy message) becomes the error message verbatim.
Source
Thrown at packages/loot-core/src/server/post.ts:38
const contentType = res.headers.get('Content-Type') ?? '';
if (contentType.toLowerCase().indexOf('application/json') !== -1) {
const json = JSON.parse(text);
throw new PostError(getServerErrorReason(json));
}
// Actual Sync Server may be exposed via a tunnel (e.g. ngrok). Tunnel errors should be treated as network errors.
const tunnelErrorHeaders = ['ngrok-error-code'];
const tunnelError = tunnelErrorHeaders.some(header =>
res.headers.has(header),
);
if (tunnelError) {
// Tunnel errors are present when the tunnel is active and the server is not reachable e.g. server is offline
// When we experience a tunnel error we treat it as a network failure
throw new PostError('network-failure');
}
throw new PostError(text);
}
}
export async function post(
url: RequestInfo,
data: unknown,
headers = {},
timeout: number | null = null,
// Optional caller-provided abort signal. Used by Enable Banking poll
// cancellation so the user can interrupt the 5-minute long-poll.
externalSignal?: AbortSignal | null,
) {
let text: string;
let res: Response;
const controller = new AbortController();
const timeoutId =
timeout != null ? setTimeout(() => controller.abort(), timeout) : undefined;View on GitHub (pinned to d4334cb6e6)
Solutions
- Inspect the error message text to identify what actually returned it (HTML page, proxy banner)
- Verify the sync server URL includes the correct host and path
- Bypass the proxy and test the server URL directly to see if the proxy is the responder
- Check proxy/auth configuration (Cloudflare Access, basic auth) for the sync endpoint
Defensive patterns
Strategy: fallback
Validate before calling
// probe the endpoint before real calls
const probe = await fetch(syncUrl);
const isJson = (probe.headers.get('Content-Type') ?? '').includes('application/json');
if (!isJson) throw new Error('Sync URL is not returning JSON — check URL/proxy'); Try / catch
try {
await post(url, data);
} catch (e) {
if (e instanceof PostError && /<html|<!doctype/i.test(e.message)) {
showSyncUrlDiagnostics(); // HTML body => wrong URL or proxy page
return;
}
throw e;
} Prevention
- Validate the sync server URL points at the API root, not a web page
- Exclude the sync endpoint from proxy auth/CDN challenge pages
- Pin the server path when upgrading (API path changes)
- Test with curl to confirm the endpoint returns JSON before configuring clients
When it happens
Trigger: Non-200 response with a non-JSON Content-Type, e.g. 404 HTML page from a wrong URL, 401 from an authenticating reverse proxy, 502/503 plain-text responses from nginx.
Common situations: Sync URL typo pointing at a web root that returns an HTML 404, reverse proxy (nginx/Cloudflare) intercepting with an error or login page, server behind maintenance mode, wrong path after server version upgrade.
Related errors
- Failed to fetch catalog: ${response.statusText}
- Failed to fetch CSS from ${url}: ${response.status} ${respon
- ${text}
- getServerErrorReason(json)
- TIMED_OUT
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/4c2923e9e594ef1a.
Report an issue: GitHub.