we-promise/sure · warning · AkahuItem::Syncer::SafeSyncError

Could not sync Akahu connection

Error message

Could not sync Akahu connection

What it means

Raised by validate_date_range! when end_date exceeds start_date + 5.years. It is a hard client-side cap on the query window for the securities screening endpoint, enforced before any request. Yahoo's screener rejects oversized windows, so the provider fails fast instead of making a doomed call. Split large windows into multiple ≤5-year chunks to fetch the full history.

Source

Thrown at app/models/akahu_item/syncer.rb:67

        window_end_date: sync.window_end_date
      )
      raise_if_failed_results!(schedule_results, stage: "Akahu account sync scheduling")

      account_ids = linked_accounts.includes(:account_provider).filter_map { |aa| aa.current_account&.id }
      collect_transaction_stats(sync, account_ids: account_ids, source: "akahu")
    else
      Rails.logger.info "AkahuItem::Syncer - No linked accounts to process"
    end

    collect_health_stats(sync, errors: nil)
  rescue SyncError => e
    collect_health_stats(sync, errors: e.sync_errors)
    raise
  rescue => e
    safe_message = I18n.t("akahu_item.errors.sync_failed")
    Rails.logger.error "AkahuItem::Syncer - Unexpected sync error: #{e.class}"
    collect_health_stats(sync, errors: [ { message: safe_message, category: "sync_error" } ])
    raise SafeSyncError.new(safe_message), cause: nil
  end

  def perform_post_sync
    # no-op
  end

  private

    def raise_if_failed_result!(result, stage:)
      return unless failed_result?(result)

      errors = errors_from_result(result, stage: stage)
      raise SyncError.new(error_message(stage, errors), sync_errors: errors)
    end

    def raise_if_failed_results!(results, stage:)
      errors = Array(results).filter_map do |result|
        next unless failed_result?(result)

View on GitHub (pinned to e69894adb9)

Solutions

  1. Chunk the request into consecutive ≤5-year windows and merge results
  2. Clamp start_date to end_date - 5.years when only recent data matters
  3. Compute the minimum number of windows needed (ceil of days/5y) in the caller
  4. Surface the cap in UI date pickers so users cannot request oversized ranges

Example fix

# before
provider.search_securities(query: q, start_date: Date.new(2000, 1, 1), end_date: Date.today)

# after
windows = []
cursor = Date.new(2000, 1, 1)
while cursor < Date.today
  windows << [cursor, [cursor + 5.years - 1.day, Date.today].min]
  cursor += 5.years
end
results = windows.flat_map { |s, e| provider.search_securities(query: q, start_date: s, end_date: e) }
Defensive patterns

Strategy: validation

Validate before calling

MAX_WINDOW = 5.years - 1.day
raise ArgumentError, "range too large" if end_date.to_date - start_date.to_date > MAX_WINDOW

Prevention

When it happens

Trigger: Passing decade-long ranges (e.g., 2010-01-01..2025-01-01) to the search/screening API; building a 'since inception' chart from first-trade date to today; defaulting start_date to a fixed epoch like 2000-01-01.

Common situations: Long-history backfills after a user changes their 'history start' setting; porting code from another provider with no window cap; tests using wide fixtures.

Related errors


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