actualbudget/actual · error

error

error

Error message

error

What it means

The POST /update-user-file endpoint failed while writing the uploaded budget file to disk (fs.writeFile inside a try/catch). The server logs 'Error writing file' with the underlying error and responds 500 with { status: 'error' } instead of storing the file and creating a new group id. This is a server-side filesystem failure, not a client payload problem.

Source

Thrown at packages/sync-server/src/app-sync.ts:366

    ? requireFileAccess(currentFile, res.locals.user_id)
    : null;
  if (fileAccessError) {
    res.status(403);
    res.send(fileAccessError);
    return;
  }

  const errorMessage = validateUploadedFile(groupId, keyId, currentFile);
  if (errorMessage) {
    res.status(400).send(errorMessage);
    return;
  }

  try {
    await fs.writeFile(getPathForUserFile(fileId), req.body);
  } catch (err) {
    console.log('Error writing file', err);
    res.status(500).send({ status: 'error' });
    return;
  }

  if (!currentFile) {
    // it's new
    const newGroupId = generateGroupId();
    groupId = newGroupId;
    filesService.set(
      new File({
        id: fileId,
        groupId: newGroupId,
        syncVersion: syncFormatVersion,
        name,
        encryptMeta,
        owner:
          res.locals.user_id ||
          (() => {
            throw new Error('User ID is required for file creation');

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the directory configured in userFiles exists and is writable by the sync-server process; create it and fix ownership/permissions (mkdir -p, chown).
  2. Check free disk space on the volume backing userFiles (df -h) and free space if full.
  3. Re-run the sync from the client after fixing storage; the client will re-upload the file.
  4. Inspect server logs for the 'Error writing file' entry to see the exact errno (ENOENT/EACCES/ENOSPC) and address that cause.

Example fix

// before (host): userFiles points at a path not created in the container
// docker run -v data:/data actual-sync  # but USER_FILES=/app/user-files missing
// after
// docker run -v data:/app/user-files -e ACTUAL_USER_FILES=/app/user-files actual-sync
// or create the dir before starting: mkdir -p "$USER_FILES"
Defensive patterns

Strategy: retry

Validate before calling

// before upload, ensure storage exists
import fs from 'fs';
if (!fs.existsSync(userFilesDir) || !fs.statSync(userFilesDir).isDirectory()) {
  throw new Error('userFiles directory missing');
}

Try / catch

const res = await uploadFile(fileId, body);
if (res.status === 500) {
  // server-side write failure: fix storage, then retry
  await ensureStorageAvailable();
  await uploadFile(fileId, body);
}

Prevention

When it happens

Trigger: Uploading a user file via POST /update-user-file when fs.writeFile(getPathForUserFile(fileId), req.body) throws — e.g. the userFiles directory is missing, disk is full, or the process lacks write permission to the directory.

Common situations: Docker volume not mounted or mounted read-only so the userFiles dir doesn't exist; full disk on self-hosted instances; running the sync server as a user without permissions on the data directory; ENOSPC/ENOENT/EACCES errors.

Understand the failure class

Background: Error: deliberate library guards and refused operations — this error's family across 54 libraries.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/f8bcf7a3a9e65913. Report an issue: GitHub.