we-promise/sure · error · WiseItem::Syncer::SafeSyncError

Sync failed. Please try again or contact support.

Error message

Sync failed. Please try again or contact support.

What it means

Raised as SafeSyncError by WiseItem::Syncer (app/models/wise_item/syncer.rb:71) after ANY exception escapes the Wise sync body. The syncer logs the original class/message and backtrace, reports it to Sentry tagged with wise_item_id, records a health-stats entry (category sync_error) using a user-safe i18n message — 'credentials_invalid' for Provider::Wise::WiseError with error_type unauthorized/access_forbidden, otherwise the generic 'Sync failed. Please try again or contact support.' — and then re-raises that sanitized message. The original error is intentionally hidden from users.

Source

Thrown at app/models/wise_item/syncer.rb:71

      )
      sync_errors.concat(result_failure_errors(schedule_results, category: :account_sync_error, message_key: :account_sync_failed))

      # Phase 5: Collect transaction statistics
      account_ids = wise_item.wise_accounts
                             .joins(:account_provider)
                             .includes(account_provider: :account)
                             .filter_map { |wa| wa.current_account&.id }
      collect_transaction_stats(sync, account_ids: account_ids, source: "wise")
    end

    collect_health_stats(sync, errors: sync_errors.presence)
  rescue => e
    safe_message = user_safe_error_message(e)
    Rails.logger.error "WiseItem::Syncer - sync failed for item #{wise_item.id}: #{e.class} - #{e.message}"
    Rails.logger.error Array(e.backtrace).first(10).join("\n")
    Sentry.capture_exception(e) { |s| s.set_tags(wise_item_id: wise_item.id) }
    collect_health_stats(sync, errors: [ { message: safe_message, category: "sync_error" } ])
    raise SafeSyncError, safe_message
  end

  def perform_post_sync
    # no-op
  end

  private

    def update_status(sync, key, **options)
      return unless sync.respond_to?(:status_text)

      sync.update!(status_text: I18n.t("wise_items.syncer.#{key}", **options))
    end

    def collect_wise_setup_stats(sync, total_count:, linked_count:, unlinked_count:)
      return unless sync.respond_to?(:sync_stats)

      merge_sync_stats(sync, {

View on GitHub (pinned to e69894adb9)

Solutions

  1. Look up the real cause in Sentry (tag wise_item_id) or the Rails log lines 'WiseItem::Syncer - sync failed for item <id>' — the SafeSyncError text alone is deliberately uninformative.
  2. If the user-facing message is the credentials-invalid variant, re-authorize/re-enter the Wise API token on the item and sync again.
  3. For transient network/API failures, retry the sync once after a short delay.
  4. If cause is unclear, reproduce with the item's credentials via Provider::Wise directly to separate auth problems from data problems.

Example fix

// before
begin
  WiseItem::Syncer.new(item).perform_sync(sync)
rescue => e
  retry # loops on the same persistent failure
end

// after
begin
  WiseItem::Syncer.new(item).perform_sync(sync)
rescue SafeSyncError => e
  Rails.logger.warn("wise sync failed safely: #{e.message}") # real cause is in Sentry
end
Defensive patterns

Strategy: try-catch

Try / catch

begin
  WiseItem::Syncer.new(item).perform_sync(sync)
rescue SafeSyncError => e
  # e.message is sanitized/user-safe; real cause is in Sentry (tag wise_item_id)
  retry if transient?(e.message) # only for known-transient messages
end

Prevention

When it happens

Trigger: Any failure inside perform_sync: Wise API 5xx/timeout, expired or revoked API tokens (WiseError unauthorized), malformed payloads, or a bug in the import pipeline. The SafeSyncError you catch is a wrapper; the real cause is only in logs/Sentry.

Common situations: Expired Wise API token triggering the unauthorized path; transient Wise outage surfacing as the generic message; users retrying sync repeatedly without effect because the underlying cause (e.g. revoked token) persists.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/53e45d33618673c2. Report an issue: GitHub.