Skip to content

[Due for payment 2026-07-21] [$500] [Mobile] Okta SSO requires double login after idle timeout — app freezes on SAML session handoff #86705

Description

@MelvinBot

If you found this issue via BugZero, please follow the BugZero triage guidelines in the wiki article here. In particular:

  • Populate the Version Number field (you can find this in the App Deployer or GitHub Releases) and change the template title.
  • If the bug is a result of a regression, label with Regression and add the Original Author - @<author> and Regression Author - @<author> labels after determining the source of the regression (you can use git bisect).
  • If unable to reproduce the bug, consider labeling with Needs Investigation and tagging relevant engineers.

Action Performed:

  1. Log into Expensify mobile app via Okta SSO
  2. Leave app idle for ~3–4 hours (aligned with Okta global session idle timeout)
  3. Return to app
  4. Complete Okta re-authentication + MFA in the in-app browser
  5. Tap "Done" on in-app browser to return to the app

Expected Result:
User is successfully authenticated and returned to the app with a persisted session — no additional login required.

Actual Result:
App freezes briefly after returning from the in-app browser, then logs the user out and redirects to the Expensify login screen. User must log in a second time (including Okta MFA). The second login works as expected.

Workaround:
User can log in a second time — the second attempt always succeeds.

Platforms:
Which of our officially supported platforms is this issue occurring on?

  • iOS
  • Android
  • Desktop
  • Web
  • Mobile Web (Chrome)
  • Mobile Web (Safari)

Version Number: Latest (reported on 9.3.39-3+ and still occurring)

Reproducible in staging?: Unknown

Logs: N/A

Notes/Photos/Videos:

Additional Context

  • Auth method: Okta SSO (SAML)
  • Okta session policy: Max session 8 hours, idle timeout 4 hours
  • Scope: Affects all mobile app users in this organization; NOT reproducible on desktop
  • Behavior is unique to Expensify — not occurring with other Okta-integrated mobile apps (Gmail, Slack, Zoom)

Technical Context from Investigation on Expensify/Expensify#613615

The mobile SAML re-authentication flow relies on a multi-redirect chain that passes a 1-minute shortLivedAuthToken back to the app via a Universal Link / deep link. When the app is backgrounded or in a transitional state during this handoff, the token can expire or the deep link can fail to fire, causing the first login attempt to silently fail.

Several server-side fixes have already been merged (Auth #19751, Web-Expensify #50652) addressing samlInfiniteMobileSessions flag preservation. However, the client-side handling of the SAML deep link callback when the app is resuming from a backgrounded state needs investigation — this is likely where the mobile-specific failure occurs.

Key areas to investigate in the App:

  • How does the React Native app handle receiving a SAML deep link callback (isSAML=true) when it has an expired session?
  • Is there a race condition between the in-app browser closing and the deep link being processed?
  • Does the app correctly handle the shortLivedAuthToken exchange when resuming from background?

Upstream issue: https://github.com/Expensify/Expensify/issues/613615

Issue OwnerCurrent Issue Owner: @mallenexpensify
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~022038834681163684626
  • Upwork Job ID: 2038834681163684626
  • Last Price Increase: 2026-06-16
  • Automatic offers:
    • thelullabyy | Reviewer | 111894633

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Mar 31, 2026
  2. MelvinBot commented on Mar 31, 2026

    @MelvinBot
    ContributorAuthor

    Proposal

    What is the root cause of that problem?

    When a mobile SAML/Okta user returns from the in-app browser (after completing MFA), a race condition occurs between two competing flows:

    1. AppState "became active" listener — fires immediately when the app resumes from the background and triggers reconnectApp() using the OLD expired authToken (NetworkConnection.ts:322-328, AuthScreensInitHandler.tsx:75-103)
    2. SAML callback — openAuthSessionAsync promise resolves with the new shortLivedAuthToken and calls signInWithShortLivedAuthToken() (SAMLSignInPage/index.native.tsx:57-61)

    If Path 1 fires before Path 2's optimistic Onyx update sets isAuthenticatingWithShortLivedToken=true, the sequence is:

    • reconnectApp() gets a 407 (NOT_AUTHENTICATED) from the server
    • Reauthentication middleware tries to re-authenticate with stored auto-generated credentials
    • For SAML users, these credentials may be empty/invalid (they signed in via SAML, not password)
    • Reauthentication fails → redirectToSignIn() → Onyx.clear() wipes ALL data including any newly set authToken (Authentication.ts:178-194, SignInRedirect.ts:84-118)
    • AuthScreens unmounts → cleanupSession() → HttpUtils.cancelPendingRequests() cancels any in-flight signInWithShortLivedAuthToken request (Session/index.ts:1008-1023)

    The second login always works because the app is in a clean state with no competing reconnection callbacks.

    Supporting evidence: Authentication.ts:133-140 has the "redirect to sign-in for missing credentials" logic commented out with a reference to #fireroom-2026-01-28-user-signout, confirming this exact pattern was already suspected.

    This is mobile-only because: (a) the in-app browser backgrounds the app, triggering AppState callbacks on resume, and (b) desktop doesn't use openAuthSessionAsync.

    What changes do you think we should make in order to solve the problem?

    In SAMLSignInPage/index.native.tsx, set isAuthenticatingWithShortLivedToken=true in Onyx before opening the in-app browser (i.e., before the openAuthSessionAsync call). This blocks the reauthentication middleware from racing against the SAML callback. Reset the flag to false if the browser is cancelled or fails.

    This ensures that when the app returns to the active state and reconnectApp() fires with the expired token, any resulting 407 response will hit the isAuthenticatingWithShortLivedToken guard in Authentication.ts:110-117 and abort reauthentication instead of triggering redirectToSignIn.

    What alternative solutions did you explore? (Optional)

    1. Suppress reconnection callbacks during SAML flow — Unregister the NetworkConnection reconnection listener before opening the in-app browser and re-register after. This is more invasive and could miss legitimate reconnection events if the network was actually lost.

    2. Add retry logic in the SAML callback error path — Currently if the guard !account?.isLoading && credentials?.login && shortLivedAuthToken fails at SAMLSignInPage/index.native.tsx:57, the flow falls through to clearSignInData() which logs the user out. Adding a retry here would be a workaround rather than fixing the root race condition.

    3. Delay the AppState "became active" reconnection callback — Add a debounce or short delay before firing reconnectApp() to give the SAML callback time to set its guard flag. This is fragile and timing-dependent.


    Next Steps for Contributor+ team: Reply with @MelvinBot implement this to create a draft PR, @MelvinBot <your feedback> to refine this analysis, or explain why you are rejecting Melvin's proposal.

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Mar 31, 2026
  4. added
    Help WantedApply this label when an issue is open to proposals by contributors
    on Mar 31, 2026
  5. melvin-bot commented on Mar 31, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @bernhardoj (External)

  6. changed the title [-][Mobile] Okta SSO requires double login after idle timeout — app freezes on SAML session handoff[/-] [+][$250] [Mobile] Okta SSO requires double login after idle timeout — app freezes on SAML session handoff[/+] on Mar 31, 2026
  7. melvin-bot commented on Mar 31, 2026

    @melvin-bot
  8. trasnake87 commented on Mar 31, 2026

    @trasnake87
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    After an Okta SSO idle timeout on mobile, completing SAML re-authentication in the in-app browser causes the app to freeze and then log the user out, forcing a redundant second login. The second attempt always succeeds.

    What is the root cause of that problem?

    When the app resumes from background with an expired SAML session, the Reauthentication middleware in src/libs/Middleware/Reauthentication.ts#L96 unconditionally calls reauthenticate() for any 407 response. For SAML users, this cascades into Authenticate() in Authentication.ts which fails at requireParameters (because SAML users have no stored autoGeneratedLogin/autoGeneratedPassword), and the catch block fires redirectToSignIn() → Onyx.clear(). This Onyx.clear() is destructive — it wipes all app data including any new session being established by the concurrent SAML re-authentication callback from openAuthSessionAsync. The middleware has no awareness that the current user is a SAML user who cannot reauthenticate with stored credentials, so it always attempts the doomed reauthentication path.

    What changes do you think we should make in order to solve the problem?

    We should add a SAML-awareness check in src/libs/Middleware/Reauthentication.ts so that when a 407 is received for a SAML user, we skip the full reauthenticate() flow (which destructively calls Onyx.clear()) and instead just invalidate the auth tokens, letting React's navigation naturally redirect to sign-in:

    // Add at module level in Reauthentication.ts
    import Onyx from 'react-native-onyx';
    import ONYXKEYS from '@src/ONYXKEYS';
    
    let signedInWithSAML = false;
    Onyx.connectWithoutView({
        key: ONYXKEYS.SESSION,
        callback: (val) => { signedInWithSAML = !!val?.signedInWithSAML; },
    });
    
    // Insert before line 96 (`return reauthenticate(...)`)
    if (signedInWithSAML) {
        // SAML users cannot reauthenticate with stored credentials.
        // Clear only auth tokens (not full Onyx.clear) to avoid destroying
        // a concurrent SAML re-auth session from the in-app browser callback.
        Onyx.merge(ONYXKEYS.SESSION, {authToken: null, encryptedAuthToken: null});
        setIsAuthenticating(false);
        if (isFromSequentialQueue) {
            return data;
        }
        if (request.resolve) {
            request.resolve(data);
        }
        return data;
    }

    This avoids the destructive Onyx.clear() for SAML users while still properly invalidating the expired session, allowing the SAML re-authentication flow to complete without interference. The subsequent openApp() call after successful SAML login will refresh all stale data.

  9. trasnake87 commented on Mar 31, 2026

    @trasnake87
    Contributor

    Contributor details
    Your Expensify account email: trasnake87@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~010f770315ab181656

  10. melvin-bot commented on Mar 31, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. wildan-m commented on Mar 31, 2026

    @wildan-m
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On mobile, when a SAML/Okta SSO user returns to the app after an idle timeout and completes re-authentication + MFA in the in-app browser, the app freezes briefly, logs the user out, and forces a second login. The second login always succeeds.

    What is the root cause of that problem?

    When the in-app browser closes and the app resumes from a backgrounded state, two competing paths fire concurrently:

    Path 1 — App resume reconnection: AppStateMonitor.addBecameActiveListener (registered by listenForReconnect()) fires immediately when the app transitions from background/inactive → active. This triggers triggerReconnectionCallbacks, which calls reconnectApp() with the old expired authToken:

    function listenForReconnect() {
    Log.info('[NetworkConnection] listenForReconnect called');
    AppStateMonitor.addBecameActiveListener(() => {
    triggerReconnectionCallbacks('app became active');
    });
    }

    NetworkConnection.listenForReconnect();
    NetworkConnection.onReconnect(() => handleNetworkReconnect());

    Path 2 — SAML callback: The openAuthSessionAsync promise resolves with the redirect URL containing the new shortLivedAuthToken, and handleNavigationStateChange calls signInWithShortLivedAuthToken():

    if (!account?.isLoading && credentials?.login && shortLivedAuthToken) {
    Log.info('SAMLSignInPage - Successfully received shortLivedAuthToken. Signing in...');
    signInWithShortLivedAuthToken(shortLivedAuthToken, true);
    return;
    }

    The reauthenticate() function has an existing guard that checks isAuthenticatingWithShortLivedToken and aborts if true:

    // Prevent re-authentication if authentication with shortLiveToken is in progress
    if (isAuthenticatingWithShortLivedToken) {
    Log.hmmm('[Reauthenticate] Authentication with shortLivedToken is in progress. Re-authentication aborted.', {
    command,
    isSupportAuthTokenUsed,
    });
    return Promise.resolve(false);
    }

    However, this flag is only set inside signInWithShortLivedAuthToken()'s optimistic data (via getShortLivedLoginParams at line 171), which runs after the browser returns — too late to block the Path 1 reauthentication that fires simultaneously on app resume.

    function getShortLivedLoginParams(isSupportAuthTokenUsed = false, isSAML = false) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.ACCOUNT | typeof ONYXKEYS.SESSION | typeof ONYXKEYS.HYBRID_APP>> = [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: ONYXKEYS.ACCOUNT,
    value: {
    ...CONST.DEFAULT_ACCOUNT_DATA,
    isLoading: true,
    },
    },
    // We are making a temporary modification to 'signedInWithShortLivedAuthToken' to ensure that 'App.openApp' will be called at least once
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: ONYXKEYS.SESSION,
    value: {
    signedInWithShortLivedAuthToken: true,
    signedInWithSAML: isSAML,
    isAuthenticatingWithShortLivedToken: true,
    isSupportAuthTokenUsed,
    },
    },
    ];

    If Path 1's reauthenticate() runs before Path 2 sets the guard, it proceeds with auto-generated credentials. For SAML users whose IdP session has expired server-side, this authentication fails — triggering redirectToSignIn() which calls Onyx.clear(), wiping all session state (including any in-flight SAML authentication) and cancelling pending requests via cleanupSession():

    return Onyx.clear(keysToPreserve).then(() => {
    if (CONFIG.IS_HYBRID_APP) {
    resetSignInFlow();
    HybridAppModule.signOutFromOldDot();
    }
    clearAllPolicies();
    // When logging out from imported state, reset shouldForceOffline to false and clear the imported state flag
    // so the user can log back in
    if (currentIsUsingImportedState) {
    Onyx.merge(ONYXKEYS.NETWORK, {shouldForceOffline: false});
    Onyx.merge(ONYXKEYS.IS_USING_IMPORTED_STATE, false);
    }
    if (!errorMessage) {
    return;
    }
    // `Onyx.clear` reinitializes the Onyx instance with initial values so use `Onyx.merge` instead of `Onyx.set`
    Onyx.merge(ONYXKEYS.SESSION, {errors: getMicroSecondOnyxErrorWithMessage(errorMessage)});
    });
    }

    function cleanupSession() {
    Pusher.disconnect();
    Timers.clearAll();
    Welcome.resetAllChecks();
    MainQueue.clear();
    HttpUtils.cancelPendingRequests();
    PersistedRequests.clear();
    NetworkConnection.clearReconnectionCallbacks();
    SessionUtils.resetDidUserLogInDuringSession();
    resetNavigationState();
    clearCache().then(() => {
    Log.info('Cleared all cache data', true, {}, true);
    });
    clearCachedAttachments();
    clearSoundAssetsCache();
    }

    This is mobile-only because: (a) openAuthSessionAsync backgrounds the app on Android and puts it inactive on iOS, triggering AppState callbacks on resume, and (b) desktop uses a different SAML implementation that doesn't use the in-app browser.

    The second login always works because the app is in a clean state with no competing reconnection flow — the AppStateMonitor.becameActive listener has already fired for that app resume cycle.

    What changes do you think we should make in order to solve the problem?

    In SAMLSignInPage/index.native.tsx, set isAuthenticatingWithShortLivedToken to true in Onyx before calling openAuthSessionAsync. This ensures the guard in reauthenticate() is active for the entire duration of the SAML browser session, blocking any reconnection-triggered reauthentication from racing with the SAML callback.

    Reset the flag to false on all non-success exit paths:

    • If the browser is cancelled (response.type !== 'success')
    • If the browser throws an error
    • If handleNavigationStateChange falls through (condition at line 57 fails)

    When signInWithShortLivedAuthToken() is called on the success path, its own optimistic data takes over flag management (sets true optimistically, resets in finallyData), so no manual reset is needed there.

    What alternative solutions did you explore? (Optional)

    1. Add a synchronous setter in Authentication.ts instead of using Onyx merge — directly setting the module-level isAuthenticatingWithShortLivedToken variable. This would avoid any theoretical async propagation delay from Onyx.merge → connectWithoutView callback. However, since the Onyx merge happens before openAuthSessionAsync (before the app backgrounds for seconds/minutes), the callback has ample time to propagate. The additional complexity of a synchronous setter is not warranted.

    2. Uncomment the credentials guard in Authentication.ts:133-140 — the code that redirects to sign-in when autoGeneratedLogin/autoGeneratedPassword are missing is currently commented out (investigating #fireroom-2026-01-28-user-signout). Uncommenting it would make reauthentication fail faster for SAML users without stored credentials, but it doesn't address the core race condition and could reintroduce the user-signout issue being investigated.

    Compare branch: https://github.com/Expensify/App/compare/main...wildan-m:App:wildan/86705-saml-sso-double-login?expand=1

  12. wildan-m commented on Mar 31, 2026

    @wildan-m
    Contributor

    Proposal

    Updated

    Added compare branch with implementation.

  13. Abubakar-01 commented on Mar 31, 2026

    @Abubakar-01

    Report - Okta SSO requires double login after idle timeout — app freezes on SAML session handoff

    Please re-state the problem that we are trying to solve in this issue.
    After an Okta SSO session times out (~4 hours idle), mobile users who successfully complete Okta re-authentication and MFA in the in-app browser are silently logged out and redirected to the login screen instead of being returned to the app. The second login attempt always succeeds. This affects all mobile users on SAML/Okta SSO and is not reproducible on desktop.
    What is the root cause of that problem?
    There are two compounding issues in the client-side SAML callback handling:
    Primary : stale account.isLoading gate in SAMLSignInPage.tsx: handleNavigationStateChange only calls signInWithShortLivedAuthToken if !account?.isLoading && credentials?.login && shortLivedAuthToken are all truthy. When a session expires, Onyx sets account.isLoading = true as part of the session cleanup. By the time the Okta callback fires with a fresh shortLivedAuthToken, account.isLoading is still true from the prior session teardown so the condition fails, falls through to clearSignInData(), and the user is logged out.
    Secondary : getLastShortAuthToken() guard in LogInWithShortLivedAuthTokenPage.tsx: This guard exists to prevent replaying stale deep links, but it can incorrectly block a legitimate re-authentication if the newly issued token matches the cached last token. When it fires, it silently returns without signing in.

    What changes do you think we should make in order to solve the problem?

    [credentials?.login, account?.isLoading, translate],

    [translate],

    if (!account?.isLoading && credentials?.login && shortLivedAuthToken) {

    if (shortLivedAuthToken) {

    if (token && !account?.isLoading) {

    if (token) {

    const [account] = useOnyx(ONYXKEYS.ACCOUNT);
    (Remove this line)

    if (!account?.isLoading && authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT) {

    if (authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT) {

    NetworkStore.setLastShortAuthToken(''); }

    What alternative solutions did you explore?

    N/A

  14. 161 remaining items

  15. mallenexpensify commented on Jul 23, 2026

    @mallenexpensify
    Contributor

    @MelvinBot payment-review
    MODE: <closed-no-pr | payment-due>
    JOB_PRICE: 250 # override if the title bounty differs

    Run the C+ payment framework for this issue and post a structured, evidence-cited
    recommendation. Rules:

    • Suggest only — never auto-pay. A human confirms.
    • Every point / adjustment MUST cite a specific comment permalink. No permalink → no point.
    • Round every %→$ result up to the nearest $25: amount = ceil((pct × JOB_PRICE) / 25) × 25.

    If MODE = closed-no-pr:

    1. Run the eligibility gate; if it fails, or the token-saving skip-gate trips
      (<~8 non-bot comments AND no C+ engagement markers, or closed <48h, or spam/invalid),
      return "$0 — skipped" with a one-line reason and STOP.
    2. Bucket the close reason (A/B/C), score the 5 factors 0–10 with permalinks.
    3. Payout = 25% of JOB_PRICE if score ≥ 8, else $0.

    If MODE = payment-due:

    1. Start at 100% of JOB_PRICE.
    2. Decreases: for any regression, read the offending PR + issue and decide if it's
      truly attributable to this author/reviewer. If yes, −50% per verified regression.
      If fault is unclear, DO NOT deduct — flag for human review.
    3. Increases: evaluate the C+ increase framework (multiple PRs, additional fixes,
      scope expansion, core-flow changes, deploy blockers audited, performance,
      test coverage, files touched), each backed by a permalink.

    Output: recommended $ amount, per-line reasoning with permalinks, confidence
    (High/Med/Low), and any manual-review flags.

  16. MelvinBot commented on Jul 23, 2026

    @MelvinBot
    ContributorAuthor

    💸 Payment Review — C+ Reviewer (thelullabyy)

    Suggestion only — not an authorization to pay. A human must confirm.

    Mode: payment-due · JOB_PRICE: $500 (overridden from $250 — title bounty is [$500])

    🧾 Recommended payout: $500 (100% of JOB_PRICE)

    Step % $ Basis
    Base 100% $500 Merged + deployed fix, C+ role
    Regression deductions 0% –$0 No verified regressions
    Increase framework 0% +$0 No qualifying uplift
    Total 100% $500

    Rounding: ceil((1.00 × 500) / 25) × 25 = $500.


    Base (100% → $500)

    thelullabyy performed the C+ review that led to a merged, deployed fix:

    • Reviewed the competing proposals and selected the approved solution — comment.
    • Was assigned the Reviewer role — offer.
    • Is the assignee on the merged PR Expensify/App#95017 (merged 2026-07-10 by grgia).
    • Completed the BugZero Checklist — comment.
    • Fix deployed to production in 9.4.33-8 on 2026-07-14.

    Regression deductions (–$0)

    • No Regression label on the issue; labels are Reviewing, External, Daily, Awaiting Payment, Bug.
    • 7-day regression period ran 2026-07-14 → 2026-07-21 with no regression reports in the thread. Title reflects [Due for payment 2026-07-21]. → No deduction.

    Increase framework (+$0)

    Evaluated each lever; none clears the bar for an uplift beyond base:

    • Multiple PRs: The reviewer did catch a real defect in the first attempt — the guard not being cleared on the no-JSON early-return path (comment) — and directed the follow-up (comment). But Expensify/App#94082 was never merged; it was superseded by Expensify/App#95017. Reviewing a superseded PR + its replacement for the same single fix is normal review scope, not a scope-expansion bonus.
    • Files touched / test coverage / core-flow: The merged PR touches 3 files and adds a regression test (tests/actions/SessionTest.ts) — but that is the PR author's work, not a reviewer uplift trigger.
    • Deploy blockers / performance: None audited or applicable.

    → No increase.


    🚩 Manual-review flags (out of framework scope, but worth a human eye before paying)

    1. Contributor-side payment is ambiguous — this review only covers the C+ reviewer. Both PRs in the merged lineage (#94082 → #95017) were authored by MelvinBot, not a human contributor. The competing human PR #94396 by trasnake87 was closed unmerged. A human should decide whether any Contributor bounty is owed and to whom.
    2. Reviewer could not independently test the SAML flow. thelullabyy repeatedly noted being unable to set up a SAML session and accepted the fix on theoretical grounds pending QA regression — comment. Confirm QA/regression verification occurred before final sign-off.

    Confidence: High on the base ($500) and the clean regression window. The two flags above are the only items needing human judgment.

  17. mallenexpensify commented on Jul 24, 2026

    @mallenexpensify
    Contributor

    @MelvinBot is @trasnake87 due payment?

  18. MelvinBot commented on Jul 24, 2026

    @MelvinBot
    ContributorAuthor

    Is trasnake87 due payment?

    Short answer: not under the standard contributor rule — their PR was never merged and they aren't a co-author of the one that was. But they were formally assigned and their bug-catch fed directly into the merged fix, so this is a discretionary call I'd flag for a human rather than a clean "no."

    Evidence

    Points against a standard bounty:

    • Their proposal was not the approved one — the reviewer selected Melvin's proposal on 2026-06-19, before trasnake87 was assigned.
    • Their PR Expensify/App#94396 was closed unmerged on 2026-06-26.
    • The merged PR Expensify/App#95017 was authored by MelvinBot; its only co-authors are roryabraham and thelullabyy — not trasnake87.
    • The reviewer explicitly declined their offer to help finish it: "Thank you but I think I can handle it with Melvin" (comment).

    Points for a discretionary payment:


    My recommendation for the human:

    Confidence: High on the facts (PR closed, not a co-author, proposal not approved). The pay/no-pay decision itself is a judgment call for you.

  19. mallenexpensify commented on Jul 24, 2026

    @mallenexpensify
    Contributor

    @thelullabyy do you think @trasnake87 is due payment? If so, how much? Please provide reasoning, details and/or links for support (I'm testing the above and might want to add something to Melv's skill/rules)

  20. thelullabyy commented on Jul 24, 2026

    @thelullabyy
    Contributor

    @mallenexpensify Sorry, I'm not sure whether @trasnake87 is due or not. This case is new to me, here is the context:

    1. We decided to go with Melvin bot proposal
    2. We was struggling with testing
    3. @trasnake87 raised a comment that he was able to test the issue with evidences
    4. We assigned @trasnake87 to open PR (same solution with Melvin) with fully tested evidences
    5. I reviewed the PR and found that the testing was not a acceptable test
    6. We decided to go back with Melvin and closed @trasnake87's PR

    I think I will let you make a call here @mallenexpensify

  21. mallenexpensify commented on Jul 24, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @thelullabyy paid $500 via Upwork

    @trasnake87 comment and tag me if you think you're due payment and I'll reopen the GH. Thx

  22. DylanDylann commented on Jul 27, 2026

    @DylanDylann
    Contributor

    @mallenexpensify Wrong Contributor+

  23. mallenexpensify commented on Jul 27, 2026

    @mallenexpensify
    Contributor

    Thanks @DylanDylann , updated.

  24. laurenreidexpensify commented on Aug 17, 2026

    @laurenreidexpensify
    Contributor

    $500 approved @thelullabyy New Expensify

  25. trasnake87 commented on Sep 23, 2026

    @trasnake87
    Contributor

    @mallenexpensify a correction for the record, since you mentioned using this thread to tune Melvin's payment rules. It's on me: Melvin's summary credits me with catching the no-json guard case, but the Codex review on #94082 flagged it first, on 06-26. My 07-01 comment also overstated things: #94396 as reviewed had the same gap, and I only added that fix to the branch after the Codex review, once the PR was already closed.

    Sorry for the confusion, and sorry I missed your note when the issue closed.

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorReviewingHas a PR in review

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions