Skip to content

[$175] Company Cards - Cannot assign new TD Canada Trust card (card ending in xxxx not listed in dashboard dropdown after successful "Assign new cards") #104352

Open

Description

@m-natarajan

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number:
Reproducible in staging?: Needs Reproduction (Reproduction blocked)
Reproducible in production?: Needs Reproduction
If this was caught during regression testing, add the test name, ID and link from BrowserStack:
Email or phone of affected tester (no customers):
Logs: https://stackoverflow.com/c/expensify/questions/4856
Expensify/Expensify Issue URL:
Issue reported by: @scerrato88
Slack conversation (hyperlinked to channel name): #Expensify Bugs

Action Performed:

  1. Connect a TD Canada Trust card feed to a workspace that already has ~50 (or more) accounts under that connection.
  2. As admin (master login), go to Settings > Assign new cards.
  3. Attempt to assign a new card ending in xxxx.
  4. Confirm the "successfully updated" message appears.
  5. Go to the card dashboard and check whether the card is listed.
  6. Check whether the company/account appears in the assign-card dropdown.

Expected Result:

The newly assigned card should appear under the card dashboard, and the company/account should be listed in the dropdown when assigning cards.

Actual Result:

The card does not appear under the card dashboard despite a successful update message.
The company is not listed in the dropdown.

Workaround:

Unknown

Needs confirmation:

Whether there's a hard cap (e.g., 50 accounts) on how many accounts Plaid/TD returns per connection

Platforms:

Select the officially supported platforms where the issue was reproduced:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

View all open jobs on GitHub

Upwork Automation - Do Not Edit

Activity

  1. MelvinBot commented on Oct 19, 2026

    @MelvinBot
    Contributor

    ⚠️ Preliminary analysis (low confidence) — treat as a starting point, not a conclusion.

    This appears to be a backend/internal issue. A separate internal investigation is needed.

    The App does not cap the assign-card list. For Plaid and direct feeds, the dropdown shows the feed's accountList exactly as the backend sends it, minus cards that are already assigned (src/libs/CardUtils.ts:1372-1380). If a TD account is missing, the backend list for that connection most likely never included it.

    The success message doesn't prove a card was created. The App adds no card on its side, so a card appears only if the backend sends one back (src/libs/actions/CompanyCards.ts:466-551).

    I couldn't verify the cause because:

    • The issue has no cardholder email, workspace or domain ID, feed name, or card last 4, so logs can't be checked.
    • The steps ("Settings > Assign new cards", "successfully updated") look like Expensify Classic, not New Expensify.

    To confirm, an internal engineer should check:

    1. How many entries the stored account list holds for this TD feed. If it holds about 50, the cap is upstream of the App.
    2. Whether the newly issued card ever reached the connection. With Plaid, accounts opened after the first connection usually need the admin to reconnect and select them. Fixing the connection in New Expensify may add them.
    3. What the backend returned for the assignment request, and whether it created a card.
    Possible App-side contributor

    getFilteredCardList hides any account whose masked name (for example CREDIT CARD...1234) matches a card assigned in any workspace the admin can see. With 50+ accounts on one login, two accounts can share a masked name. isCardAlreadyAssigned was already limited to the current workspace for this reason, but getFilteredCardList was not. This could hide an account from the dropdown. It does not explain a card missing from the dashboard after a successful assignment.


    Next Steps: Reply with @MelvinBot <your feedback> to refine this analysis. Adding the cardholder email, workspace ID, and card last 4 would let the logs be checked.


    view run

  2. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 19, 2026
  3. melvin-bot commented on Oct 19, 2026

    @melvin-bot

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

  4. changed the title [-]Company Cards - Cannot assign new TD Canada Trust card (card ending in xxxx not listed in dashboard dropdown after successful "Assign new cards")[/-] [+][$175] Company Cards - Cannot assign new TD Canada Trust card (card ending in xxxx not listed in dashboard dropdown after successful "Assign new cards")[/+] on Oct 19, 2026
  5. melvin-bot commented on Oct 19, 2026

    @melvin-bot
  6. camesenin commented on Oct 19, 2026

    @camesenin

    Proposal

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

    When assigning a company card from a TD Canada Trust feed (or any direct Plaid/OAuth feed with many accounts/workspaces), a newly added card does not appear in the card assignment dropdown or is omitted from the dashboard dropdown after assignment.

    What is the root cause of that problem?

    In src/libs/CardUtils.ts:1361-1380 inside getFilteredCardList:

    // Get cards assigned across all workspaces
    const allWorkspaceAssignedCards = new Set<string>();
    for (const workspaceCards of Object.values(workspaceCardFeeds ?? {})) {
        forEachAssignedCard(workspaceCards, (card) => {
            if (!card.cardName) {
                return;
            }
            allWorkspaceAssignedCards.add(card.cardName);
        });
    }
    
    // For direct feeds (Plaid/OAuth): displayName === cardIdentifier
    if (accountList) {
        return filterAmexDirectParentCard(accountList, feedName)
            .filter((cardName) => !assignedCards.has(cardName) && !allWorkspaceAssignedCards.has(cardName))
    1. For direct feeds (Plaid/OAuth), the client-side identifier is the masked card name (e.g., TD Business...1234 or CREDIT CARD...xxxx), which is not unique across accounts or workspaces when an enterprise has 50+ cards.
    2. In isCardAlreadyAssigned (src/libs/CardUtils.ts:2242-2251), Expensify engineers explicitly noted:

      "Only flag a card already assigned within the CURRENT workspace/domain (and feed). Direct feeds (Plaid/OAuth) must NOT be matched across other workspaces: the only identifier the client has for them is the masked display name... Genuine cross-account duplicates are rejected server-side by ASSIGN_COMPANY_CARD — the same path Expensify Classic uses, which is why the identical assignment succeeds there."

    3. While isCardAlreadyAssigned was corrected to scope to the current workspace, getFilteredCardList was inadvertently left accumulating allWorkspaceAssignedCards across every workspace the admin has access to.
    4. Consequently, if another card in any workspace shares the same masked name suffix, allWorkspaceAssignedCards.has(cardName) returns true, erroneously filtering the card out of the assignment dropdown in NewDot, while Classic succeeds without cross-workspace client-side filtering.

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

    In src/libs/CardUtils.ts:

    1. In getFilteredCardList, remove cross-workspace filtering for direct feeds (accountList). Only filter out assignedCards belonging to the specific current workspace and feed being configured:
      if (accountList) {
          return filterAmexDirectParentCard(accountList, feedName)
              .filter((cardName) => !assignedCards.has(cardName))
              .map((cardName) => ({
                  cardName,
                  cardID: cardName,
              }));
      }
    2. For commercial feeds, preserve workspace scoping consistent with isCardAlreadyAssigned.

    What alternative solutions did you explore? (Optional)

    Attempting to synthesize unique composite keys from client data. This was rejected because Plaid/OAuth feeds provide the masked account name as the display key, and the server-side command (ASSIGN_COMPANY_CARD) already enforces true database-level duplicate validation.

    Automated Tests

    In tests/unit/CardUtilsTest.ts:

    • Add test case verifying that getFilteredCardList displays a direct feed card in workspace A even when a card with an identical masked name exists in workspace B under workspaceCardFeeds.
    • Assert that only cards assigned within the target workspace/feed (assignedCards) are filtered out.

    Contributor details
    Your Expensify account email: [email protected]
    Upwork Profile Link: https://www.upwork.com/freelancers/~0170d6b02e9440e604

  7. melvin-bot commented on Oct 19, 2026

    @melvin-bot

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

  8. j3ffffff commented on Oct 19, 2026

    @j3ffffff

    VERDICT: POST
    ROOT_CAUSE_ONELINER: After importPlaidAccounts succeeds, the client's CARD_FEEDS Onyx key is never refreshed — successData only clears ASSIGN_CARD.isRefreshing, so CardSelectionStep's accountList-based card list remains stale and the new card never appears.
    PROPOSAL_TYPE: FIRST
    AUTOPOST_SAFE: YES

  9. sijo0218 commented on Oct 19, 2026

    @sijo0218

    Contributor details
    Your Expensify account email: [email protected]
    Upwork Profile Link: https://www.upwork.com/freelancers/~011091133bbb09cd1c

    Proposal

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

    When assigning a company card from a TD Canada Trust feed (or any direct Plaid/OAuth feed with ~50+ accounts or across multiple workspaces), newly connected/assigned cards do not appear in the assignment dropdown or are missing from the dashboard dropdown even after a "successfully updated" confirmation message.

    What is the root cause of that problem?

    There are two root causes:

    1. Cross-Workspace Filtering in getFilteredCardList (CardUtils.ts:1361-1380):
      In src/libs/CardUtils.ts:1361-1375, getFilteredCardList aggregates all assigned cards across every workspace via allWorkspaceAssignedCards:
    const allWorkspaceAssignedCards = new Set<string>();
    for (const workspaceCards of Object.values(workspaceCardFeeds ?? {})) {
        forEachAssignedCard(workspaceCards, (card) => {
            if (!card.cardName) {
                return;
            }
            allWorkspaceAssignedCards.add(card.cardName);
        });
    }

    And then filters direct feeds (accountList) with:

    .filter((cardName) => !assignedCards.has(cardName) && !allWorkspaceAssignedCards.has(cardName))

    For direct feeds (Plaid/OAuth), cardName is merely the masked account title/suffix (e.g. TD Business...xxxx or CREDIT CARD...xxxx), which is not unique across accounts or workspaces when an enterprise has 50+ cards.
    As explicitly documented in isCardAlreadyAssigned (src/libs/CardUtils.ts:2242-2248):

    "Direct feeds (Plaid/OAuth) must NOT be matched across other workspaces: the only identifier the client has for them is the masked display name... which is not unique across accounts. Genuine cross-account duplicates are rejected server-side by ASSIGN_COMPANY_CARD — the same path Expensify Classic uses, which is why the identical assignment succeeds there."

    While isCardAlreadyAssigned was correctly updated to restrict checks to the current workspace, getFilteredCardList was left filtering cross-workspace. As a result, if any card in another workspace shares an identical masked display name, allWorkspaceAssignedCards.has(cardName) returns true and silently strips the card from the dropdown.

    1. Stale CARD_FEEDS in importPlaidAccounts (Plaid.ts:184-198):
      In src/libs/actions/Plaid.ts:184-198, importPlaidAccounts executes API.write(WRITE_COMMANDS.IMPORT_PLAID_ACCOUNTS, parameters, onyxData). However, successData only clears ASSIGN_CARD.isRefreshing: null. It never merges or refreshes CARD_FEEDS in Onyx. Because CardSelectionStep.tsx:60 and getFilteredCardList read cardFeeds?.[feed]?.accountList, the client retains the stale account list in Onyx after the Plaid import finishes until the user explicitly refreshes the app.

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

    1. Remove Cross-Workspace Filtering for Direct Feeds:
      In src/libs/CardUtils.ts:1361-1380, align getFilteredCardList with the architectural pattern in isCardAlreadyAssigned: for direct feeds (accountList), only filter against assignedCards within the current workspace and feed:
    if (accountList) {
        return filterAmexDirectParentCard(accountList, feedName)
            .filter((cardName) => !assignedCards.has(cardName))
            .map((cardName) => ({
                cardName,
                cardID: cardName,
            }));
    }
    1. Refresh CARD_FEEDS upon Successful Plaid Import:
      In src/libs/actions/Plaid.ts:184-198, ensure importPlaidAccounts updates CARD_FEEDS or refetches the policy card feeds upon completion so that cardFeeds?.[feed]?.accountList contains the freshly imported cards immediately.
    2. Tests:
      In tests/unit/CardUtilsTest.ts:
    • Add a test verifying that getFilteredCardList keeps a direct feed card available for assignment in workspace A even when an identical masked name exists in workspace B in workspaceCardFeeds.
    • Assert that only cards assigned within the target workspace/feed (assignedCards) are filtered out.

    What alternative solutions did you explore? (Optional)

    Attempting to synthesize composite keys on the client using account indexes or hashes. This was rejected because Plaid direct feeds use the masked account name as the display key, and backend duplicate checking in ASSIGN_COMPANY_CARD already authoritatively prevents actual duplicate card assignments without needing client-side cross-workspace collisions.

  10. melvin-bot commented on Oct 19, 2026

    @melvin-bot

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

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

Metadata

Metadata

Assignees

Labels

#feeds-2026BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions