actualbudget/actual · error

Health check failed: Server responded to health check with s

Error message

Health check failed: Server responded to health check with status ${status}

What it means

Thrown by the health-check script when it fetches /health, parses the JSON, and getHealthStatus returns something other than 'UP'. The status (e.g. 'DOWN' or 'DEGRADED') is embedded in the message; the script's catch handler logs and exits non-zero so orchestrators mark the server unhealthy.

Source

Thrown at packages/sync-server/src/scripts/health-check.ts:27

  if (
    typeof response === 'object' &&
    response !== null &&
    'status' in response &&
    typeof response.status === 'string'
  ) {
    return response.status;
  }

  return undefined;
}

fetch(`${protocol}://${hostname}:${config.get('port')}/health`)
  .then(response => response.json())
  .then(response => {
    const status = getHealthStatus(response);

    if (status !== 'UP') {
      throw new Error(
        'Health check failed: Server responded to health check with status ' +
          status,
      );
    }
  })
  .catch(err => {
    console.log('Health check failed:', err);
    process.exit(1);
  });

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Read the reported status and server logs to find why /health reports DOWN (usually DB access).
  2. Verify the account database path/permissions in the sync server config.
  3. Retry after migrations/startup complete; add startup grace periods to your orchestrator.
  4. Confirm the script targets the correct host/port where the Actual sync server listens.

Example fix

// before
Health check failed: Server responded to health check with status DOWN
// after
# fix DB path, then
yarn workspace @actual-app/sync-server health-check   # status UP, exit 0
Defensive patterns

Strategy: retry

Validate before calling

// only run the health check after the server is listening
await waitForPort(config.get('port'), { timeoutMs: 30000 });
// then: const res = await fetch(`http://host:${port}/health`); await res.json();

Type guard

function isHealthCheckFailure(err: unknown): err is Error {
  return err instanceof Error && err.message.startsWith('Health check failed:');
}

Try / catch

const run = async (retries = 5) => {
  for (let i = 0; i < retries; i++) {
    try { return await healthCheck(); }
    catch (err) {
      if (isHealthCheckFailure(err) && i < retries - 1) { await sleep(5000); continue; }
      throw err;
    }
  }
};

Prevention

When it happens

Trigger: Running the health-check script against a server whose /health reports DOWN — e.g. the account database is unreachable, migrations failed, or the sync server is up but its internal dependencies are broken.

Common situations: Docker/Kubernetes health probes failing during startup before the DB is ready; misconfigured sqlite path making the server report DOWN; using the script against the wrong port or a different service that answers with non-standard health JSON.

Related errors


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