Skip to content

Conversation

@Jon-edge
Copy link
Collaborator

@Jon-edge Jon-edge commented Feb 10, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Context

Asana: "Fix 'Security Check' notification trigger (reset PW)" (P0). After dismissing the Security Check modal once, it never reappears on subsequent logins — even though it should show within two non-password logins. This is the only way for users who forgot their password but can still PIN/bio login to reset it.

Regression introduced in 4.42.0 by commit ef36815d47 which consolidated ACCOUNT_INIT_COMPLETE into the LOGIN action, changing the timing of settings initialization.

Changes

PasswordReminderService is a class component that unmounts on logout and remounts after LOGIN dispatches. Because Redux state already contains the incremented nonPasswordLoginsCount by the time the component mounts, componentDidUpdate never fires for the initial login transition — the "previous" and "current" props are identical from its perspective.

This means the reducer-computed password reminder state (including the login counter) was never persisted to disk on login, so the count stayed stuck at a stale value across login cycles and the notification threshold was never reached.

Fix: Add componentDidMount to PasswordReminderService to persist the current reducer state to disk when the component mounts with loaded settings. This ensures the nonPasswordLoginsCount advances on disk across login cycles without undoing the performance improvements from the original LOGIN consolidation.

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Note

Low Risk
Small, localized lifecycle change that only affects when password-reminder settings are written to disk; low risk aside from potential extra write on mount.

Overview
Fixes a regression where the Security Check/password reminder state wasn’t being persisted on login, preventing the notification from re-triggering across subsequent non-password logins.

PasswordReminderService now persists the current passwordReminder to disk in componentDidMount once settingsLoaded is true (in addition to the existing componentDidUpdate persistence), ensuring the login counters/state advance even when the service remounts with already-updated redux state. Adds a CHANGELOG entry noting the fix.

Written by Cursor Bugbot for commit ab3211f. This will update automatically on new commits. Configure here.

PasswordReminderService unmounts on logout and remounts after LOGIN
dispatches, so componentDidUpdate never fires for the initial LOGIN
transition. Add componentDidMount to persist the reducer-computed
passwordReminder state (including the incremented nonPasswordLoginsCount)
on mount so the count advances on disk across login cycles.
@Jon-edge Jon-edge force-pushed the jon/fix/security-check-notification branch from e968a00 to cb243ec Compare February 10, 2026 02:28
Copy link
Contributor

@swansontec swansontec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just replacing the PasswordReminderService with this:

import * as React from 'react'

import { setPasswordReminder } from '../../actions/LocalSettingsActions'
import { useAsyncEffect } from '../../hooks/useAsyncEffect'
import {
  initialState,
  type PasswordReminderState
} from '../../reducers/PasswordReminderReducer'
import { useDispatch, useSelector } from '../../types/reactRedux'
import { matchJson } from '../../util/matchJson'

interface Props {}

export const PasswordReminderService: React.FC<Props> = props => {
  const settingsLoaded =
    useSelector(state => state.ui.settings.settingsLoaded) ?? false
  const passwordReminder = useSelector(state => state.ui.passwordReminder)
  const lastPasswordReminder = React.useRef<PasswordReminderState>(initialState)
  const dispatch = useDispatch()

  useAsyncEffect(
    async () => {
      if (
        settingsLoaded &&
        !matchJson(passwordReminder, lastPasswordReminder.current)
      ) {
        lastPasswordReminder.current = passwordReminder
        await dispatch(setPasswordReminder(passwordReminder))
      }
    },
    [settingsLoaded, passwordReminder],
    'PasswordReminderService'
  )

  return null
}

That way the useAsyncEffect runs not only on update, but also on first mount. At the same time, the code gets shorter & more modern.

}

render() {
render(): React.JSX.Element | null {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be React.ReactElement for the return type, but for some reason revcode isn't flagging it. I guess edge-conventions has too many rules to hold the agent's attention.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — changed return type to React.ReactElement | null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants