actualbudget/actual · error

response.reason || response.error_code

Error message

response.reason || response.error_code

What it means

`ensureSuccessResponse` validates the sync-server's response after setting bank-sync provider secrets (GoCardless, SimpleFin, PluggyAI, EnableBanking, Akahu). If the response carries an `error_code`, it throws with `response.reason || response.error_code` — preferring the human-readable reason but falling back to the raw error code string. This surfaces provider-configuration rejections from the sync server (e.g. missing/invalid API keys) to the UI.

Source

Thrown at packages/desktop-client/src/components/banksync/useBuiltInBankSyncProviders.ts:99

  isFileOwner: boolean,
): 'general' | 'file-owner' | null {
  if (syncServerStatus !== 'online') {
    return null;
  }

  if (isAdmin) {
    return null;
  }

  return isFileOwner ? 'file-owner' : 'general';
}

async function ensureSuccessResponse(
  response: SecretSetResponse,
  fallbackMessage: string,
) {
  if (response?.error_code) {
    throw new Error(response.reason || response.error_code);
  }

  if (response?.error) {
    throw new Error(response.reason || response.error || fallbackMessage);
  }
}

export function useBuiltInBankSyncProviders({
  upgradingAccountId,
}: UseBuiltInBankSyncProvidersOptions = {}) {
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const syncServerStatus = useSyncServerStatus();
  const { cloudFileId, isAdmin, isFileOwner } = useCurrentAccess();
  const canConfigureProviders = isAdmin;

  const [isGoCardlessSetupComplete, setIsGoCardlessSetupComplete] = useState<
    boolean | null

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Read the thrown message — it is the `reason` or `error_code` from the sync server; map known codes (e.g. invalid-credentials) to the offending field.
  2. Re-enter the provider credentials carefully (no trailing spaces, correct environment — sandbox vs production keys).
  3. Check sync-server logs and its provider env configuration (e.g. ACTUAL_GOCARDLESS_* variables) for the underlying cause.
  4. Restart/upgrade the sync-server if the code indicates an internal storage failure, then retry.

Example fix

// before
const res = await send('secret-set', { name: 'gocardless_secret_id', value: value.trim() });
await ensureSuccessResponse(res, 'Failed to set GoCardless secret');
// after
const value = input.trim();
if (!value) { setError('Secret ID is required'); return; }
const res = await send('secret-set', { name: 'gocardless_secret_id', value });
try {
  await ensureSuccessResponse(res, 'Failed to set GoCardless secret');
} catch (e) {
  setError(e.message); // surfaces e.g. 'invalid-credentials' from server
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before submitting credentials
const trimmed = value.trim();
if (!trimmed) { setError('Value is required'); return; }
if (provider === 'gocardless' && !/^test_|^live_/.test(trimmed)) {
  setError('GoCardless secret IDs start with test_ or live_');
  return;
}

Type guard

function isSecretSetError(
  res: SecretSetResponse,
): res is SecretSetResponse & { error_code: string } {
  return typeof res?.error_code === 'string' && res.error_code.length > 0;
}

Try / catch

try {
  await ensureSuccessResponse(res, 'Failed to set secret');
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e);
  if (/credential|invalid|unauthorized/i.test(msg)) {
    setError('Credentials rejected — verify the keys for your provider environment (sandbox vs production).');
  } else {
    setError(msg);
  }
}

Prevention

When it happens

Trigger: Any reset/save handler (`onGoCardlessReset`, `onSimpleFinReset`, `onPluggyAiReset`, `onEnableBankingReset`, `onAkahuReset`) submits credentials via the sync-server and the server responds with `error_code` set — e.g. secret storage failed, the provider rejected the credentials, or the request was malformed/unauthorized.

Common situations: Entering an expired or wrong GoCardless secret ID/token; PluggyAI clientId/secret rejected; EnableBanking or Akahu credentials invalid; sync-server cannot persist secrets (storage/permission problem); running without the required env vars on a self-hosted server.

Related errors


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