actualbudget/actual · error · FileUploadError
internal
internal
Error message
internal
What it means
FileUploadError('internal') is thrown when the upload POST itself throws but the error is not a PostError — i.e., an unexpected exception (fetch transport crash, serialization bug, unhandled promise rejection) rather than a structured server response. It is a catch-all for non-classified upload exceptions.
Source
Thrown at packages/loot-core/src/server/cloud-storage.ts:363
: null),
...(groupId ? { 'X-ACTUAL-GROUP-ID': groupId } : null),
// TODO: fix me
// oxlint-disable-next-line typescript/no-explicit-any
},
body: uploadContent,
});
} catch (err) {
logger.log('Upload failure', err);
if (err instanceof PostError) {
throw FileUploadError(
err.reason === 'unauthorized'
? 'unauthorized'
: err.reason || 'network',
);
}
throw FileUploadError('internal');
}
if (res.status === 'ok') {
// Only save it if we are still working on the same file
if (prefs.getPrefs() && prefs.getPrefs().id === id) {
await prefs.savePrefs({
lastUploaded: monthUtils.currentDay(),
cloudFileId,
groupId: res.groupId,
});
}
} else {
throw FileUploadError('internal');
}
}
export async function possiblyUpload() {
const { cloudFileId, groupId, lastUploaded } = prefs.getPrefs();View on GitHub (pinned to d4334cb6e6)
Solutions
- Check the app logs: 'Upload failure' is logged with the original error before this is thrown — fix the underlying cause
- Retry the upload; transient transport errors often resolve
- Verify the server URL configuration (serverURL) is a valid absolute URL
- Update Actual — newer versions classify more failure modes properly
Example fix
// before: opaque internal failure
await upload();
// after: capture underlying cause via logs and retry with backoff
for (let i = 0; i < 3; i++) {
try { await upload(); break; }
catch (e) { if (e.reason !== 'internal') throw e; await delay(1000 * 2 ** i); }
} Defensive patterns
Strategy: retry
Validate before calling
function assertValidServerUrl(url) {
const u = new URL(url);
if (!/^https?:$/.test(u.protocol)) throw new Error(`invalid server URL: ${url}`);
return url;
} Type guard
function isInternalUploadError(e) {
return e instanceof FileUploadError && e.reason === 'internal';
} Try / catch
try {
await upload();
} catch (e) {
if (isInternalUploadError(e)) {
logger.error('upload internal failure — check underlying cause in logs', e);
await backoffRetry(upload, 2); // may be transient transport issue
} else throw e;
} Prevention
- Check the logged 'Upload failure' cause before assuming the worst
- Keep Actual updated so more failure modes are classified properly
- Validate server URL configuration at startup
- Retry with exponential backoff for transient transport errors
When it happens
Trigger: the fetchJSON call in upload() throws an exception that is not an instance of PostError — e.g. an abrupt network socket error, request construction failure, or an error thrown inside header/body preparation.
Common situations: proxy or TLS middleware throwing, undici/node fetch low-level failures, misconfigured server URL producing malformed requests, memory pressure with very large budgets.
Related errors
- download-failure
- Could not resolve on-disk budget id for syncId ${syncId} aft
- results.reason || results.error_code
- results.reason || results.error
- Failed to fetch catalog: ${response.statusText}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/a92f091d186b8bba.
Report an issue: GitHub.