Skip to main content

AI-detected merchants were created as shared records

When merchant detection identified a new merchant from a transaction’s description, Sure previously created a globally-shared ProviderMerchant record (source: ai). Because those records are visible to every family on the deployment, a transaction description crafted to steer the LLM’s output could create or match a shared merchant with an attacker-chosen name, website, or logo — affecting all other families.
The Family::AutoMerchantDetector class, which runs as part of the background merchant detection job. When no existing merchant could be matched for a transaction, it fell back to creating a ProviderMerchant (source: ai) scoped globally rather than to the family.Reusing an already-existing shared merchant by website or name was not affected — that path does not write new data into shared records.
When AI detection cannot find a shared merchant to reuse, it now creates a FamilyMerchant scoped to the requesting family instead of a global ProviderMerchant. AI-sourced merchants from before this fix are also limited to the family that already has them on its transactions — they are no longer matched by name or URL across families.Upgrade to the latest release to get this fix. No configuration change is required.

Account name and balance visible to family members without access after a sync

In earlier versions, when an account finished syncing, Sure broadcast a rendered account row — including the account name and balance — on the family’s shared Turbo stream. Every signed-in family member subscribed to that stream, including members who did not have access to the account. The row reached their browser after every sync even though the accounts page did not show it to them.
Any account that could sync independently — linked accounts (connected via a provider) and manual accounts that were synced — could expose their name and balance to all currently signed-in family members, regardless of whether those members had view or edit access to the account.
The broadcast that replaced the account row in the family stream is removed. Instead, when a sync completes, Sure sends a lightweight sync toast to each browser. The browser refreshes through its own authenticated request, so only members with access see the account data. Refresh can be deferred during interaction with forms or dialogs, and a manual refresh may be needed.The fix also covers linked accounts that sync on their own with no follow-up provider event, which would previously have left the accounts page stale for members who do have access.Upgrade to the latest release to get this fix. No configuration change is required.
Opening the Settings link from a transactions list filtered by a large number of accounts could result in a 500 error (CookieOverflow). The transactions list passes the current page’s full URL — including all selected account IDs — as a ?return_to= query parameter so Sure can return you to the same filtered view after you leave settings. With enough accounts selected, that URL exceeds the 4 KB limit of the cookie-backed session.
Sure stores the ?return_to= value in the session so it can redirect back to the page you came from after completing an action in settings. The session is stored entirely in an encrypted browser cookie. When the return_to URL contains dozens of account IDs as query parameters, the resulting cookie size exceeds 4 KB and Rails raises a CookieOverflow error, producing a 500 response.
When the return_to value exceeds 1 KB, Sure now stores only the path without its query string. If even the bare path is over 1 KB, the return_to value is discarded entirely and the previous stored value is also cleared so a stale redirect target is never used. When the shortened path fits, Sure retains it as the return destination without the filters. Only when the path is discarded does Sure fall back to the default destination.Upgrade to the latest release to get this fix. No configuration change is required.

”All” chart period starts later than expected

When you select the All period on a net worth or account chart, the chart now starts one month before the earliest Transaction or Trade entry across your family — not your earliest entry of any kind. In earlier versions, Sure used the oldest entry of any type to anchor the All period. A single valuation or opening balance dated years in the past stretched every All-range chart back to that date, leaving a long flat line with no activity before any real spending or trading began.
The All period now anchors to one month before the first Transaction or Trade entry across your family. Valuations and opening balances no longer affect how far back the All range extends.If your family has only valuations and no Transactions or Trades, the All period uses the family’s oldest entry date when it is before today; a missing, today, or future start falls back to five years ago.
If you had a valuation or opening balance dated further back than your first real transaction, the All chart will now start later. The data is unchanged — only the visible range has shifted to focus on the period where you have actual activity.Budgets and the AI balance sheet are not affected. They still use the oldest entry date as before.

Mixed-currency arithmetic produces wrong totals silently

Earlier versions of Sure allowed arithmetic operations (+, -, /, <=>) between Money objects in different currencies. The result kept the left operand’s currency and discarded the right operand’s — so 200 USD - 180 EUR returned 20 USD with no error or log line. This affected syncs, transfer matching, insight generators, and some report pages for families that hold accounts in more than one currency.
Any calculation that combined two Money values from different currencies without an explicit exchange_to conversion first. Common surfaces include:
  • Balance and net worth totals across multi-currency accounts
  • Transfer matching when accounts are in different currencies
  • Insight generators and assistant read functions
  • Report and API responses for families mixing currencies such as EUR, GBP, and USD
Money arithmetic now checks that both operands share the same currency before proceeding.In development and test, a mismatch raises Money::CurrencyMismatchError immediately so the call site is easy to find.In production, the mismatch is reported through Rails.error and logged at warn level (deduplicated by operation, currency pair, and call site within a bounded per-process cache) and the operation keeps its previous result so no page starts failing while the root cause is fixed. Equality comparisons between different currencies return false without raising.Versions containing currency-mismatch detection enable it without an additional configuration change.
A log line like [Money] Can't apply + to USD and EUR amounts; convert one with exchange_to first means Sure found a call site that combines amounts in two different currencies without converting first.The log includes the call site location. In most cases the fix is to call exchange_to on one of the amounts before the operation, passing the target currency and the relevant date.If you are self-hosting a customized version of Sure, search for the call site identified in the log and add the conversion. On an unmodified install, upgrade to the latest release — the known call sites are being fixed progressively.

Confirmation dialogs could run script from bank-provided names

In earlier versions, confirmation dialogs (such as those shown when deleting a provider merchant, resetting holdings, or replacing a SimpleFIN account) interpolated user-supplied names directly into the dialog body via innerHTML. Names sourced from bank feeds, brokerage providers, or other family members could contain HTML that the browser executed as script. Because provider merchant rows are shared across families, a crafted merchant name could reach any user who opened a confirmation involving that merchant.
Confirmation dialogs that showed a record name in the body text, including:
  • Provider merchant deletion confirmations (the merchant name came from bank feeds and was shared across families)
  • SimpleFIN account replacement prompts (account name from the bank)
  • SnapTrade brokerage and holdings reset confirmations (brokerage name and ticker symbols from the provider)
  • Any other confirmation built through CustomConfirm or the raw turbo_confirm hash that included a record name
The confirmation dialog now sets its body using textContent instead of innerHTML. Record names are rendered as plain text regardless of how the confirmation was constructed, so HTML in a name is never parsed or executed.Upgrade to the latest release to get this fix. No configuration change is required.

MXN amounts show wrong number separators in Spanish

When the interface language is set to Spanish, Mexican peso (MXN) amounts were formatted using European-style separators — a comma as the decimal separator and a dot as the thousands separator. This produced values like 1.234,56 MXN instead of the correct 1,234.56 MXN.
Sure formats currency amounts using the active locale’s separator rules. Spanish uses European-style separators for most currencies, but MXN natively uses US-style separators (dot as decimal, comma as thousands). The formatter did not have a currency-specific override for MXN under the Spanish locale, so it applied the locale’s default separators instead.
The number formatter now checks the currency code before applying locale-derived separators. When the currency is MXN and the active locale is Spanish, Sure retains MXN’s dot decimal separator and comma thousands separator. Other locales keep their existing formatting rules. All other currencies continue to follow the locale’s own separator conventions.Upgrade to the latest release to get this fix. No configuration change is required.

Category avatar preview does not update when picking a color

When editing or creating a category, changing the color using the color picker previously did not update the avatar preview — the icon and background appeared to stay at the original color until the form was saved.
The category avatar uses a --avatar-color CSS variable to compute both the background (a light tint) and the icon color (the full color). Earlier versions of the color picker updated the avatar’s inline style.color and style.backgroundColor directly instead of setting the variable. Overriding the resolved values bypassed the theme-aware color-mix logic, so the preview stayed stuck at the last saved color.
The color picker now updates the --avatar-color CSS variable on the avatar element when you select a new color. The avatar’s CSS reads from the variable, so both the background tint and the icon color update immediately as you pick.Upgrade to the latest release to get this fix. No configuration change is required.

Account balance or net worth drops to zero unexpectedly

If an account balance or your net worth chart falls to zero for a day — without any transactions that explain it — a provider may have returned a zero or invalid exchange rate for that currency pair on that date. Storing a zero rate converts every balance in that currency to zero.
Exchange rate providers occasionally return a zero rate for a single-day lookup. Earlier versions of Sure stored whatever value the provider returned. Any conversion that used that rate multiplied the balance by zero, wiping the reported value for that day. The bulk importer was already protected, but single-rate lookups were not.
Sure now validates every rate — whether from a single lookup or a series — before storing it. A finite, positive number is required. Rates that are zero, negative, NaN, or infinite are rejected:
  • A single-rate lookup that returns an unusable value is treated as a failed response; that response supplies no replacement rate. Any fallback depends on the caller.
  • Rates in a bulk series that are unusable are dropped. The importer can fill subsequent gaps from a usable prior rate. Without an earlier anchor it begins at the earliest usable provider date; if no usable rates are returned, it does not fill the series.
  • A database check constraint rejects zero, negative, and non-finite values for any write that bypasses model validations, such as bulk upserts.
  • Any provider rate rejected by this validation is recorded in Settings → Debug under the exchange_rates category so you can see which provider returned the bad value and on which date.
A rate of exactly 1 is valid and is stored normally (used for pegged currencies).Existing invalid rates in the database are removed when you upgrade. The next sync refetches those dates from the provider.Versions containing this validation need no configuration change to enable it.
Go to Settings → Debug and filter by the exchange_rates category. Entries naming the concrete exchange-rate provider class, with level warn, show the provider name, the currency pair, and the invalid rate that was rejected.

Account shows an error when a foreign-currency transaction has no exchange rate

If an account holds transactions in a currency that has no exchange rate available for a given date, earlier versions would abort the entire balance calculation for that account, marking it as errored even though all transactions had imported correctly.
Balance calculation converts each entry into the account’s base currency. When no exchange rate exists for the currency pair on a specific date, Sure previously raised an error that escaped the calculation loop. A single unconvertible entry was enough to produce no balances at all and leave the account in an error state.
Sure now skips the unconvertible entry rather than aborting. The entry is excluded from the balance total, the missing rate pair is written to Settings → Debug under the sync category, and the rest of the account’s transactions are still used to calculate balances normally.Holdings with a missing rate are handled differently: they are still counted toward the total at a 1:1 rate (to avoid phantom cash appearing in the cash balance), and the occurrence is also logged.To see which rate was absent, go to Settings → Debug and filter by the sync category. Look for entries from Balance::SyncCache with level warn.Upgrade to the latest release to get this fix. No configuration change is required.
Go to Settings → Self-Hosting and confirm an exchange rate provider is configured. Twelve Data is the default. After configuring or re-enabling a provider, trigger a manual sync on the affected account. Once rates are fetched, the previously skipped entries will be included in the next balance calculation.

Dragging a section on the reports page activates mid-scroll on mobile

On mobile, a brief pause while scrolling the reports page could accidentally activate drag mode on a reorderable section, visibly shifting the section and interrupting the scroll.
The reports page lets you reorder sections by deliberately holding a section and dragging. The previous hold delay was 150ms with no movement threshold, so a normal scroll gesture — a short pause followed by a swipe — was enough to trigger drag mode mid-scroll. Once drag mode activated, the section moved with the finger instead of the page scrolling.
The hold delay is raised to 800ms and a 10-pixel movement threshold is added. If the touch moves more than 10px before the hold delay expires, the pending drag is cancelled and native scrolling continues uninterrupted. A touchcancel event (for example, from an interrupting system gesture) now also discards the pending reorder instead of committing it.Upgrade to the latest release to get this fix. No configuration change is required.

Every click on the reports page navigates to a transaction drill-down in Safari

In Safari, clicking anywhere on the reports page — including the period picker or other controls — could unexpectedly navigate to a category’s transactions drill-down instead of performing the intended action. On iOS Safari, the same layout also caused scrolling to freeze for the entire page.
The earlier category-row implementation used an absolutely positioned stretched-link overlay. The fix addresses Safari reports of that overlay intercepting clicks outside the intended cells and iOS scrolling freezing. The current source also documents WebKit table-layout scroll-height problems with positioned overlays.
The stretched-link overlay is replaced with normal-flow <a> elements wrapping the icon, name, and entry count in the category cell, and separate <a> elements to the same destination in the amount and percentage cells. The clickable cells no longer rely on a positioned overlay anchored to a table row.Upgrade to the latest release to get this fix. No configuration change is required.

Debugging rule failures

When an async rule run fails (for example, during a family sync or after a provider webhook), the error is written to the debug log. To view it:
  1. Go to Settings → Debug
  2. Filter by the rules category
Each entry includes the error message and the rule that failed. This is useful for diagnosing why auto-categorization stopped working after a provider or model change.

When Rules Are Triggered

Rules run automatically during family syncs or can be triggered manually. Understanding when rules execute helps troubleshoot categorization issues.
Rules can be manually triggered in the following ways:
  • Clicking “Re-apply” on an individual rule in /settings/rules
  • Clicking “Apply All” in /settings/rules
  • Running the rake task: bin/rails rules:apply_all[family_id]
Rules with active: true run automatically when a family sync occurs (Family::Syncer.perform_sync):Scheduled syncs:
  • SyncAllJob - runs daily at 2:22 AM for all families
  • SyncHourlyJob - runs every hour (for items that opt-in to hourly syncing)
Triggered syncs:
  • Provider webhooks (Plaid, etc.) - triggers sync which eventually propagates to family
  • Manual sync button on accounts page
  • After CSV imports complete (Import model calls family.sync_later)
Rules are not triggered in these scenarios:
  • Manual transaction creation - Only triggers an Account sync (not Family sync)
  • Manual transaction editing (balance-affecting fields) - Changing the amount, date, or account triggers an Account sync (not Family sync). Editing only descriptive fields — name, notes, category, merchant, or tags — does not trigger any account sync. Reports and budgets still pick up the change through the entries cache version bumped by the save.
  • Inactive rules (automatic syncs) - Rules with active: false never run during automatic family syncs
Apply All is an exception: it runs all rules, active and inactive, so their transaction changes (category, merchant, tags, and so on) apply. Inactive rules skip the email notification action only. Matches seen while a rule is off stay unrecorded; they are handled when the rule is switched back on.
Failures during async rule runs (including auto-categorize provider failures) are written to the debug log. To view them:
  1. Go to Settings → Debug
  2. Look for entries with category rules
Each failed run includes the error message and the rule that triggered it.

Rule run failures not visible in the UI

When rules run asynchronously (during a sync or via a background job), failures are not surfaced in the main UI. If auto-categorization or merchant detection appears to stop working silently, check the debug log.
Failures from background rule runs — including auto-categorize provider errors — are written to the debug log. To view them:
  1. Go to Settings → Debug
  2. Filter by the relevant category (e.g., rules or auto_categorization)
A request that was enqueued but has no matching start entry in the log means the job never reached a worker.

Category Rule Popup Behavior

When you select a category for a transaction, a prompt appears to create an automatic categorization rule. This prompt has two controls:
  • X (close) — removes this individual prompt only. It makes no server call and does not affect future prompts.
  • Hide for today — hides all rule suggestion prompts for 24 hours. A note below the buttons reads “Hide for today pauses these suggestions for 24 hours.” so the behavior is always visible.
There is also a Don’t show this again checkbox that permanently disables rule prompts for your account. The prompt is gated by several conditions:
The prompt only shows when all of these conditions are met:
  1. You have not disabled rule prompts (rule_prompts_disabled is false)
  2. You have not clicked Hide for today in the last 24 hours
  3. The transaction category actually changed
  4. No active transaction rule that sets this category already matches this specific transaction
  5. The transaction has a category assigned
Check these common reasons:
  • Recently snoozed: You clicked Hide for today — wait 24 hours for prompts to resume
  • A matching rule already exists: An active rule whose conditions match this transaction already sets this category. A rule that sets the same category but whose conditions do not match this transaction does not suppress the prompt.
  • Prompts disabled: Check if rule_prompts_disabled is enabled for your account (set via Don’t show this again)
  • Prompt appeared behind an open drawer: In older versions, the prompt could be rendered while the transaction drawer was still open. The browser renders native dialogs in a top layer above all other content, so the prompt sat behind the drawer unseen. A page refresh then removed it before the drawer closed. This is fixed in the current release — the prompt now waits until the drawer closes before appearing.

Sure Doesn’t Work Over HTTPS

If Sure behaves incorrectly or fails when accessed over HTTPS, it’s usually due to missing SSL-related configuration between Nginx and Rails.
Sure relies on correct protocol headers to determine whether a request is secure.
When HTTPS is terminated at Nginx but not properly forwarded to the app, Rails may treat requests as HTTP.
Apply the following configuration changes:
  1. Nginx Ensure HTTPS is forwarded to the upstream app:
  2. docker-compose.yml In the x-rails-env: &rails_env section, set:
    This tells Rails to treat all requests as HTTPS.

Some pages break over HTTPS

Sure uses WebSockets for certain pages. If some pages fail to render correctly or break entirely, this is often caused by missing SSL or upgrade headers in your reverse proxy configuration.
You may see pages partially load, fail completely, or errors similar to:
This indicates that the WebSocket upgrade request is not being forwarded correctly.
Ensure your Nginx configuration includes the required WebSocket headers:
These headers allow Nginx to properly handle WebSocket upgrade requests over HTTPS.
Self-hosted setups using a forward-auth proxy (Authelia, Authentik, Traefik forward-auth, etc.) may see 500 errors when clicking the Income or Expense drill-down links on the dashboard’s Money In / Out widget. The links work fine when accessed directly.
The drill-down links previously included every accessible account ID as explicit query parameters. With many accounts, the resulting URL could exceed several thousand characters. Forward-auth proxies forward the full URL to the auth service in a request header; if that header exceeds the proxy’s read-buffer or header-size limit, the request fails with a 500 before it reaches Sure.
Sure now omits the account_ids parameter when the widget’s selected accounts exactly match the user’s full set of accessible accounts — the same default the transactions page uses when no filter is applied. The result is identical, but the URL stays short.When a family has accounts excluded from reports or tax-advantaged accounts (so the eligible set differs from the accessible set), the IDs remain explicit to preserve the correct scope.Upgrade to the latest release to get this fix. No configuration change is required.
If you cannot upgrade immediately, increase the header buffer size in your proxy. For Nginx acting as a forward-auth proxy:
For Traefik, set forwardAuth.tls.insecureSkipVerify is unrelated — instead configure your auth service to accept larger headers.

Holdings list is slow with many securities

When an investment account holds a large number of securities, opening the holdings list in the web app could be noticeably slow in earlier versions.
The holdings list loaded each holding’s associated security in a separate database query — one query per row. With many holdings, this produced a large burst of queries, causing the page to load slowly.
The holdings controller now fetches all securities for the current account’s holdings in a single query before rendering the list. This replaces the per-row security lookups with eager loading, reducing the number of database queries needed to render the list.Upgrade to the latest release to get this fix. No configuration change is required.

Rule prompt disappears after changing category from the transaction drawer

In older versions, changing a transaction’s category from the transaction detail drawer did not show the “create a rule?” prompt. The prompt was delivered while the drawer was still open, which placed it behind the drawer in the browser’s rendering stack where it could not be seen or clicked. A sync completing shortly after then refreshed the page and removed it entirely.
The transaction drawer uses a native browser <dialog> element, which the browser places in a special top layer above all other page content. The rule prompt was streamed into the notification tray at the same time as the drawer was open. Because the notification tray sits below the top layer, the prompt was invisible and unclickable. When the next sync completed and morphed the page, the prompt was removed before the drawer ever closed.
The notification tray now defers streams targeting the prompt area and page-refresh streams while a dialog is open. Other streams continue normally. Once the last dialog closes, deferred prompts render first. A deferred refresh is skipped while a prompt is visible, preventing these background refreshes from erasing an unread prompt.Upgrade to the latest release to get this fix. No configuration change is required.

Account disappears after a failed delete

If you delete an account and it vanishes from every list and the balance sheet but is never actually removed, one possible cause is that the background deletion job was not queued successfully. A job that fails during destruction is a separate case, described below.
Deleting an account marks it pending_deletion and queues a background job to remove it. In earlier versions, the status was committed to the database before the job was pushed to the queue. If the job push failed at that point — because the queue (Redis) was temporarily unreachable or the push was rejected — the account was left marked pending_deletion with no job coming to finish the deletion. Because pending_deletion accounts are excluded from every visible list, the account appeared to vanish. There was no AASM event to leave pending_deletion, so you could not retry from the UI.
Sure now restores the account’s previous status when the job push fails, so the account reappears in your list instead of staying hidden. The deletion attempt also runs under a row lock to prevent a double-submit from capturing a stale status. If the push was rejected, a failure alert is shown and you can try again once the queue is healthy.Upgrade to the latest release to get this fix. No configuration change is required.
If an account is already stuck in pending_deletion on an older version, verify that your worker container and Redis are healthy:
Once the worker is running and Redis is reachable, a server administrator can reset the account’s status directly in the database, then attempt the deletion again from the UI.

Account reappears as disabled after a failed deletion

When you delete an account, Sure marks it as pending deletion and then runs a background job to remove it. If the background job encounters an error during destruction, the account was previously left in the pending deletion state — hidden from every account list, ineligible for any further action, and impossible to re-delete. The only way out was a direct database fix.
The deletion request marks the account pending_deletion before queuing the background job that attempts destruction. If the destroy raises an error mid-run — for example, a database constraint violation or a provider API failure — the job’s own error handler reset a scheduled_for_deletion flag that belongs to provider items, not to accounts. The account was left stuck in pending_deletion with no job queued to retry it.
Account#destroy now catches destruction errors and fires a restore_after_failed_deletion event, which transitions the account from pending_deletion back to disabled. The account reappears in your account list and can be deleted again once the underlying problem is resolved.For accounts that were already disabled before deletion was attempted, the original disabled_at timestamp is preserved so the net worth chart cutoff is not moved forward to the failure date.Upgrade to the latest release to get this fix. No configuration change is required.
If an account is already stuck in pending deletion on an older version, it requires a direct database update to restore it. Set the account’s status column back to disabled so it becomes visible and can be re-deleted from the UI. After upgrading, an exception raised during destruction restores the account to disabled so you can retry.

Dashboard loads slowly with many accounts

When a user has access to many accounts (through family sharing or account groups), earlier versions issued a separate database query to load accessible accounts on each access during a single request. This caused the dashboard to slow down noticeably as the account count grew.
Current.accessible_accounts was computed fresh on every call within a request cycle. Controllers and views that accessed Current.accessible_accounts multiple times — common on the dashboard, which renders several widgets — each triggered a new database query. With a large number of shared accounts, this produced many redundant queries per page load.
Current.accessible_accounts is now memoized for the duration of each request. The query runs once and the result is reused across every controller and view that accesses it. The memoized value is cleared automatically when the request context resets.Current.finance_accounts received the same fix.Upgrade to the latest release to get this fix. No configuration change is required.

Transactions disappear from income, spending, or budgets after a provider sync

If one or more transactions vanish from your income, spending, or budget totals after a sync — without being deleted — one possible cause in older versions is a provider removing a transaction that was part of a confirmed transfer.
A confirmed transfer links two transactions and assigns transfer-specific kinds such as cc_payment or funds_movement. A pending match leaves the existing kinds unchanged. When a provider removes a pending transaction that is one leg of a transfer, the transfer itself is destroyed. In older versions, deleting a transfer through that cascade path skipped the step that resets the surviving transaction’s kind back to a standard value. The surviving transaction kept its transfer kind — cc_payment or funds_movement — with no transfer behind it, which caused it to be silently excluded from income, spending, and budget calculations.One possible trigger involves pending transactions from provider feeds: a provider may post a pending charge, the transfer is confirmed in Sure, and then the provider cancels or replaces the pending entry before it settles.
The kind reset now runs as a before_destroy callback on the transfer, so every deletion path — including cascades triggered by deleting a transaction or its entry — resets the surviving leg’s kind to the appropriate standard value. Pending transfers keep their existing behaviour and are not affected.Upgrade to the latest release to get this fix. No configuration change is required.
The callback prevents this problem when a transfer is deleted; it does not repair transactions orphaned before the upgrade. A manual sync is not a general repair for those records. Ask your server administrator to investigate the affected transactions and correct their kinds only after confirming they are no longer transfers.To find potentially affected transactions, look for transactions with no linked transfer that have a kind of cc_payment or funds_movement on accounts where those kinds are unexpected.

Deleting an account with many transfers is slow

When you delete an account, Sure removes all transfers linked to its transactions. In older versions, this process issued a separate database query for each transfer’s associated transaction records, causing it to slow significantly when an account had hundreds of transfers.
Sure now loads all transfer transaction associations in a single query before starting the deletion loop. Deleting an account with a large transfer history completes in roughly constant time regardless of the number of transfers involved.Upgrade to the latest release to get this fix. No configuration change is required.

Provider connection stuck on “Deletion in progress”

If a provider connection shows “Deletion in progress” indefinitely and never disappears, the background deletion job may not have been queued successfully.
When you delete a provider connection, Sure marks it as scheduled for deletion and enqueues a background job to remove it. If the job queue (Redis) was unreachable at that moment — or if an enqueue callback rejected the job — the flag stayed set with no job queued. The connection dropped out of the active list and could never be synced again, but it was never actually deleted.
Sure now restores the deletion flag when the enqueue fails, so the connection returns to its normal state rather than staying stuck. If the job queue is temporarily unavailable, you can retry the deletion once the queue is healthy again.Upgrade to the latest release to get this fix. No configuration change is required.
If a connection is already stuck on an older version, triggering a fresh deletion from Settings → Providers will re-enqueue the job once the queue is reachable. If the connection does not respond to deletion, verify that your worker container and Redis are healthy:
Once the worker is running and Redis is reachable, attempt the deletion again.

Cash flow and income reports show stale data after deleting an account

After you delete an account, cash flow charts, income statements, and category spending totals could continue to include that account’s transactions until an unrelated change triggered a cache refresh.
The shared income statement freshness key already included the most recent account update timestamp, along with entry changes, exchange rates, and family currency. However, an account timestamp alone can return to an earlier value after an account is marked for deletion and then destroyed, allowing an older cached result to be reused. The family and category statistics caches also used only the entry version rather than the shared freshness key.
On main, the shared freshness key uses an account version containing both the account count and the full-precision latest update timestamp. The count changes when an account is destroyed, preventing the key from reverting to its pre-deletion value. Family and category statistics now use the shared freshness key too, so a new report request refreshes those results when account status or family currency changes.This fix was merged in Sure PR #4235. As of October 20, 2026, it is on main but is not included in the latest stable release, v0.7.5-hotfix.2. Use a build containing that fix, or wait for a stable release that includes it. No configuration change is required.

Reports show stale data after merging or deleting a category

When you merge categories or delete a category, dashboards and income statements could continue to display the deleted category until an unrelated transaction change occurred.
Category merges and category deletions reassign transactions in bulk using update_all, which skips Active Record callbacks. The cache key for family reports is driven by the updated_at timestamp of the most recently changed entry. Because update_all bypassed callbacks, the entry timestamps were never updated, so the report cache key did not change and the old category data remained visible.
Sure now routes all bulk category reassignments through Transaction.reassign_category!, which updates the affected transactions and then touches the corresponding entries inside a single database transaction. Touching the entries bumps their updated_at values, which invalidates the report cache key immediately.The entry touch is batched in groups of 5,000 to avoid building an unbounded UPDATE statement on large merges.Upgrade to the latest release to get this fix. No configuration change is required.

Budget page shows a 500 error when expected income is not set

If the budget page crashes with a server error after the AI assistant sets a spending budget without also setting an expected income, the page can fail when rendering the income-side calculations.
The budget model’s initialized? check only looks at budgeted_spending, so a budget with spending set but a nil expected_income passes that check and the page attempts to render. Earlier versions of expected_income_money, actual_income_percent, remaining_expected_income, and surplus_percent did not guard against a nil value, causing a division-by-zero or nil error during render.This could happen when the AI assistant’s update_budget tool was asked to set only a spending amount — it did not automatically fill in expected_income.
The income-side budget methods now treat a nil expected_income as zero, matching how the spending side already behaved. surplus_percent no longer divides by zero.In addition, when update_budget sets budgeted_spending without an expected_income and the budget’s existing income is nil, it fills in expected_income = 0. An existing income amount is preserved. This matches what the budget form saves when you leave the income field empty.Upgrade to the latest release to get this fix. No configuration change is required.

Budget totals do not update after deleting transactions

If a budget still shows spending from a transaction that was already deleted, the most likely cause is a stale aggregate cache. Sure hard-deletes the transaction entry, so the deleted transaction is gone; the stale value can come from cached budget or income totals that were not invalidated by deleting an older entry.
Budget totals are derived from entry aggregation queries. Older versions of Sure used the most recent entry update timestamp as part of the cache key, which meant deleting an older transaction could leave the cache key unchanged.
The entry aggregate cache version now includes both:
  • The current entry count
  • The newest entry updated_at timestamp
Because the count changes when an entry is deleted, budget and income aggregate caches are invalidated even when the deleted transaction was not the most recently updated entry.
Confirm the installed version includes the cache invalidation fix for Family#entries_cache_version, then re-check the affected budget after the next request or cache refresh. The regression test is:

Chat fails immediately on a fresh family with a strict OpenAI-compatible provider

Some strict OpenAI-compatible providers (not OpenAI itself) reject chat requests with a schema validation error when a family has no accounts, categories, merchants, tags, or tickers yet.

Rule runs fail silently

If auto-categorization or merchant detection rules appear to do nothing, the background job may have failed without surfacing an error in the UI.
Async rule run failures are now logged to the debug log. Go to Settings → Debug and look for entries from the rule_run category. Each failed run includes the error class, message, and the rule that triggered it.You can also check worker logs directly:
Common causes include:
  • LLM provider errors — The AI provider returned an error or timed out. Check your OPENAI_ACCESS_TOKEN and provider connectivity.
  • Auto-categorize provider failures — The provider returned an unexpected response format. These are now propagated and logged rather than silently swallowed.
  • Worker not running — Rules run in the background via Sidekiq. Verify the worker container is healthy.

App crashes for a family with an invalid timezone

If a family’s timezone setting contains an unrecognized IANA zone name (for example, after a tzdata rename like Europe/Kiev → Europe/Kyiv), Sure now falls back to the application default timezone instead of crashing.

Chat fails immediately on a new account with a strict OpenAI-compatible provider

Some strict OpenAI-compatible providers (not OpenAI itself) reject chat requests with a schema validation error when a family has no tags, merchants, or categories yet. This happens because the assistant’s tool schemas include enum fields built from family data — an empty family produces enum: [], which is invalid JSON Schema.
Sure builds tool schemas for the assistant using family data (account names, categories, merchants, tags, tickers). A family with none of this data produces enum: [] for those fields, which is invalid JSON Schema. OpenAI tolerates it, but strict providers reject the entire request before the model runs. Family#timezone is a free-text field. If the stored value becomes stale — due to a tzdata rename, a database restore from an older backup, or a direct database edit — the app previously raised an ArgumentError on every request for that family, including the login page.
Sure validates the timezone value against ActiveSupport::TimeZone before using it. If the value is unrecognized, the app falls back to the default timezone and logs a warning to Settings → Debug under the timezone category. The log is debounced to once per day per family so it does not flood the debug log.To fix the root cause, update the family’s timezone to a valid IANA zone name from Settings → Profile. Sure builds tool schemas dynamically from your family’s data (account names, categories, merchants, tags, tickers). When none of that data exists yet, the schema contains enum: [], which is invalid JSON Schema. OpenAI tolerates it, but strict OpenAI-compatible providers reject the entire request before the model runs.
Empty enum arrays are now pruned from tool schemas before the request is sent, falling back to a plain string type. This fix applies to both chat tool definitions and the /mcp endpoint’s tools/list.If you are on an older version and cannot upgrade, add at least one account, category, or tag to the family — this populates the enum and avoids the empty-array case.

Chat shows “assistant not available” before the model finishes

Self-hosted users running a local model (Ollama, LM Studio, etc.) may see the chat fail with “assistant not available” even though the model is still generating a reply and tokens are being billed.
Sure uses a whole-turn watchdog (AI_RESPONSE_TIMEOUT, default 90 seconds) that starts when the message is queued. Custom OpenAI-compatible providers use a synchronous code path — nothing renders until the full reply is generated — so the watchdog fires before the model finishes.Tool-using turns make this worse: each tool call adds another full model round. The total time the watchdog must cover is:
With the defaults (ASSISTANT_MAX_TOOL_CALL_ITERATIONS=8, OPENAI_REQUEST_TIMEOUT=60), a worst-case turn can take up to 540 seconds of model time — far beyond the 90-second default.
The most effective approach is to lower ASSISTANT_MAX_TOOL_CALL_ITERATIONS (reducing the worst-case bound) and then size AI_RESPONSE_TIMEOUT using the formula above.For a local Ollama setup:
You can also set Chat Response Timeout from Settings → Self-Hosting → AI Provider in the UI. The environment variable takes precedence.See Chat response timeout for full sizing guidance.

Account creation form clears all fields when a loan is saved

When you clicked Create Account on a loan (or any account with nested fields such as an interest rate), the form could come back completely blank if the save failed after account-level validation passed. The failure happened inside the opening balance entry or during attribute locking, so the rescued exception held an Entry record rather than the account the form rendered. Sure rebuilt a blank account instead of the one you submitted, discarding every field you had typed — including the account name and the loan rate.
Any manual account creation that failed after the account model itself was valid. This was most likely to surface when:
  • A loan with nested fields (interest rate, term, etc.) was submitted with values that caused the opening balance entry to raise an error.
  • The attribute lock step failed during create_and_sync.
On failure, the form came back empty, forcing users to re-enter every field.
Sure now rebuilds the account from the submitted parameters on a failed save, applying the same default for nested accountable attributes that create_and_sync uses. The form comes back filled in with everything you typed so you can correct the specific error and resubmit without losing your work.Upgrade to the latest release to get this fix. No configuration change is required.

”Record payment” button does nothing in the manual payment form

If you clicked Record payment in the manual payment panel of the bill occurrence drawer, the button previously did nothing. Only pressing Enter while focused inside the amount or date field submitted the form. The button now submits the form correctly when clicked.
The DS::Button component defaults to type="button" to avoid accidentally stealing Enter-key form submission from other buttons on the page. The Record payment button was not overriding that default, so clicking it fired no action. Only pressing Enter in one of the form fields triggered the submission.
The Record payment button is now rendered with type="submit", which makes it behave as a standard form submit button.Upgrade to a version containing this fix. No configuration change is required.

”Add transaction” button does nothing when no account is selected

If you click Add transaction on the new transaction form without selecting an account, the button previously appeared to do nothing (the request returned a 404 error). The form now re-renders with a validation error so you can see what is missing and correct it.

Split transactions not grouped in account activity

When the Group split transactions preference was enabled, split child entries were collapsed under their parent on the Transactions page but still appeared as flat, ungrouped rows on the Activity tab of individual account pages. Split transaction grouping now applies consistently to both the Transactions page and the account activity feed. When the preference is on, split children appear indented under their parent row in both views.

Excluded transactions disappear from account activity

Transactions marked as excluded were previously hidden from the account activity feed entirely. This made it impossible to re-include them because there was no row to click. Excluded transactions now appear greyed-out in the account activity list so you can still open them and toggle exclusion off if needed.

Rule rejected with an invalid field error

If saving a rule or importing rules fails with an “invalid” validation error on a name, condition value, or action value, the field likely contains a null byte (\0) or a malformed UTF-8 sequence. PostgreSQL rejects strings with null bytes, and Sure now catches them before they reach the database.
The error appears when:
  • A rule name, condition value, or action value was copied from a source that embedded a null byte (for example, certain export tools or scripted database edits).
  • A CSV or NDJSON import file contains rule rows with binary garbage or truncated multibyte characters in any text field.
  • A condition type, action type, or resource type field in a submitted form contains a malformed encoding — in that case the form returns an error as plain text rather than re-rendering.
  • Manually created rules: Open the rule in Settings → Rules, clear and retype the affected field, then save.
  • Imported rules: Open the import file in a text editor, find the row flagged in the error, remove or replace any non-printable characters in the name, condition value, or action value column, and re-run the import.
  • CSV files: Ensure the file is saved as UTF-8 without BOM and without embedded null bytes. Most spreadsheet apps can re-export a clean UTF-8 file.
Upgrade to the latest release to get this fix. No configuration change is required.

Rule category, merchant, or tag names missing in exports

When you export your data (via Settings → Exports), rule conditions and actions that reference a category, merchant, or tag should appear with the human-readable name rather than the raw UUID. In older versions, two bugs could cause the name to be missing or stale in the exported rules.csv and all.ndjson files:
  • Uppercase UUID stored in the rule — Rule operand values are stored as UUIDs. If a UUID was stored in uppercase (for example, after a direct database edit or migration), the export lookup was case-sensitive and would miss the match, leaving the raw UUID in place of the name.
  • Stale in-memory association — If a category, merchant, or tag was created or modified during the same request that triggered the export (for example, as part of an import or rule-apply job), the exporter could read a stale in-memory association and produce an outdated name or omit the value_ref entirely.
Both bugs are fixed in the current release. UUID lookups are now case-normalized before the cache lookup, and operand records are reloaded from the database at the start of each export rather than using a cached association. To resolve: Upgrade to the latest release and re-run the export. No data migration or configuration change is required. If you previously imported an export that contained raw UUIDs instead of names in rule operand fields, update those rule conditions and actions manually after upgrading.

Rule creation from modal fails with “must have at least one action”

When creating a rule from the rule creation modal, saving the form could fail with a validation error saying the rule must have at least one action — even though you had added one. The re-rendered form also lost any dynamically added action or condition rows.
The rule form uses dynamically generated keys to identify newly added action and condition rows before they are persisted. A previous change introduced string keys of the form new_1, new_2 for these rows. Rails strong parameters only treat integer-keyed hashes as nested attributes — it checks that the key matches /\A-?\d+\z/ — so string keys were silently dropped before the record was saved. The result was a rule with no actions, which failed validation.
The key generator now produces numeric-only keys by combining the current timestamp with a per-session sequence counter (Date.now() * 1000 + seq). This avoids collisions with the numeric indexes Rails assigns to already-persisted rows on edit forms, and ensures all dynamically added rows pass Rails’ nested-attribute check.Upgrade to the latest release to get this fix. No configuration change is required.

Import fails on rules with no name or deleted rejected transfers

Importing an all.ndjson export could fail with two unrelated errors:
  • Rule with "name": null — The import preflight incorrectly required a name for every rule, but the rule model allows a null name. The preflight now requires the rule’s id instead, matching what the importer actually uses.
  • RejectedTransfer referencing a deleted transaction — A rejected transfer whose original transaction was later deleted caused the import to abort with a hard error. These orphaned rows are now skipped with a warning instead of blocking the entire import.
If a previous import attempt failed for either of these reasons, retry the import after upgrading.

Sure import fails with unresolved merchant references from an older export

Exports produced before provider-merchant support was added did not include ProviderMerchant rows in the all.ndjson file. Importing such a file into a current version of Sure would previously fail preflight with a blocking error for every transaction whose merchant_id pointed at a provider-assigned merchant.
An unresolvable merchant_id on a transaction or recurring transaction is now a preflight warning rather than a blocking error. Sure imports the row without a merchant assigned. The confirm screen shows the number of affected records under a “merchant references not found” notice.Unnamed recurring transaction series that have neither a name nor a resolvable merchant are skipped entirely (and reported separately). Named series without a merchant import normally.
Re-export from the source instance using a current version of Sure. The exporter now includes all ProviderMerchant records referenced by the family’s transactions and recurring transactions. Importing that newer export resolves the merchant references without warnings.If the source instance is no longer available, the missing merchants are rebuilt automatically the next time Sure syncs the family’s connected accounts.

Editing a provider merchant’s website had no effect in older versions

In versions before the fix in #3824, submitting a website-only edit on a provider-assigned merchant (one created automatically by bank sync or AI enrichment) updated the shared merchant record for every family on the instance. If you had a same-name family merchant already, the edit form reported success but silently kept the old URL because the existing family merchant was not updated.
Changing the name or website of a provider-assigned merchant now converts it to a family-owned merchant scoped to your family only. Other families on the instance are unaffected. If your family already has a merchant with the same name, Sure reuses that record and applies the submitted website (including a blank value to clear it). A submitted color is also applied to the reused merchant.Saving the edit form without making any changes still keeps the shared provider merchant as-is.
Open Settings → Merchants, find the merchant, and submit the edit form with the correct website. The current version will apply your change to a family-owned copy of the merchant.

Sure import fails because categories or tags already exist on the destination family

When a fresh family is created, Sure seeds it with a default set of categories. Importing a Sure export from another instance into that family previously failed preflight if any exported Category, Tag, or Merchant names matched the existing defaults.
Name collisions on family-owned taxonomy records (Category, Tag, and family Merchant) are now preflight warnings rather than blocking errors. Sure reuses the existing record and updates it with the imported data. The confirm screen lists how many records were reused.
Yes. Transactions and rules that referenced the imported category or tag are remapped to the surviving (reused) record. The imported data updates that record’s fields. No manual cleanup is needed.

Balance anchor dated away from provider holdings

When Sure syncs an investment or crypto account, it compares the date of the balance anchor (the recorded current balance) with the date of the newest holding that the provider reported. If the two dates differ, Sure writes a warning to the debug log so you can investigate possible date-related cash artifacts in balance calculations.
Some providers date their holdings to the day the data was produced (for example, the previous trading day’s close), while the balance anchor is set to the day of the sync. A gap between the anchor date and the holdings date can cause the reverse balance calculator to interpret differences in valuation as cash.Interactive Brokers and Plaid are known examples of providers that can produce this gap.Manual accounts, accounts without a current balance anchor, and accounts whose holdings carry no provider attribution are not checked and will never produce this warning.
Go to Settings → Debug and filter by the provider_sync category. Look for warn-level entries with a message like:
Each entry includes the provider, the account, both dates, and the gap in days as structured metadata. A steady gap does not add a new entry on each sync — only a change in the gap (including the gap closing) is recorded.When the gap closes, Sure logs an info-level entry:
This warning is informational for self-hosted operators. The warning itself does not change account data. Investigate it if the account balance or cash history looks wrong.If you see cash appearing and disappearing in an investment account’s balance chart on a one-day cycle, check Settings → Debug for a provider_sync warning on that account. The gap days value confirms a date mismatch, which may help explain the anomaly; it does not by itself prove the cause.The warning does not indicate a sync failure. The sync completes normally; only the debug entry is written.

Reading auto-categorization debug logs

When auto-categorization runs, Sure writes structured entries to the debug log under the auto_categorization category. Go to Settings → Debug and filter by auto_categorization to see them.
Each run can produce the following entries:
  • AI categorization cache used (info) — logged when some transactions in the batch already have an AI-set category that matches their current category. Those transactions are skipped to avoid redundant API calls. The entry includes the IDs of the cached transactions.
  • AI categorization blocked by enrichment protection (info) — logged when some transactions have a locked category_id attribute that was not set by AI. Those transactions are protected from being overwritten. The entry includes the IDs of the blocked transactions.
  • AI categorization failed: no categories available (error) — logged when the family has no categories configured. Auto-categorization cannot run without categories to map against.
  • AI categorization completed (info) — logged at the end of a successful run. The entry includes the requested transaction IDs, which transactions were categorized, which were cached or blocked, and the total number of modified transactions.
Check the debug log for auto_categorization entries. Transactions may be skipped because:
  • They already have an AI-assigned category that matches the current value (cache hit — shown in the cache used entry).
  • Their category_id attribute is locked by a non-AI source (shown in the blocked entry).
  • The family has no categories set up (shown in the no categories available error entry).

Why is Sure not running auto-categorization and merchant detection on the same transactions again?

Sure caches AI-generated results to avoid redundant API calls and costs. Once a transaction has been processed by AI rules, it won’t be re-processed unless you explicitly reset the AI cache.
When AI rules process transactions, Sure stores:
  • Enrichment records - Which attributes were set by AI (category, merchant, etc.)
  • Attribute locks - Prevents rules from re-processing already-handled transactions
This caching ensures:
  • Transactions aren’t sent to the LLM repeatedly
  • API costs are minimized
  • Processing is faster on subsequent rule runs
To have AI rules re-process transactions, you need to reset the AI cache:
  1. Go to Settings → Rules
  2. Click the menu button (three dots)
  3. Select Reset AI cache
  4. Confirm the action
After resetting, the next time rules run (either manually or during a sync), AI will re-process all transactions.Note: This will incur API costs if using a cloud provider.The reset runs in the background. You can track its progress and see how many AI cache entries were removed in Settings → Debug, filtered by the ai_cache_reset category. Each run logs when it starts, how many entries were removed per scope, and any records that could not be cleared.
Common scenarios for resetting the cache:
  • Switching LLM models - Different models may produce better categorizations
  • After system updates - New versions may have improved prompts
  • Fixing miscategorizations - When AI made systematic errors
  • Testing - During development or evaluation of AI features
The AI cache is automatically cleared when you change the OpenAI model setting.
The reset runs as a background job. You can confirm it completed — and see a breakdown of how many AI cache entries were removed — in the debug log:
  1. Go to Settings → Debug
  2. Filter by category ai_cache_reset
You will see:
  • An info entry when the reset is enqueued from the rules page
  • An info entry when the job starts on a worker
  • A completion entry with the number of enrichments deleted, broken down by scope
  • warn entries for any individual records that could not be cleared
  • An error entry if the job could not be enqueued at all
If you see the enqueue entry but no start entry, the job never reached a worker — check that your worker container is running.