Repository navigation
[$250] Advanced Tag filter: mixed 'No tag' + real tag selection does not return both untagged and tagged expenses #92604
Description
Problem
When the advanced Tag filter is used with a mixed selection of No tag together with one or more real tags (e.g. No tag + Test), the filter does not behave like an expected OR-style multi-select filter. It does not return both untagged expenses and expenses with the selected tag.
This is a follow-up to Expensify/App#91083 / PR #91652, which fixed the standalone No tag case. The mixed case was deliberately left out of scope. See the original investigation: #91652 (comment) and the request to split it out: #91652 (comment)
Root cause
No tag and real tags are represented by different query concepts:
- A real tag is represented as
tag:<tag name>. - An untagged expense is represented as
-has:tag.
PR #91652 fixes the standalone No tag case by converting tag: [none] into -has:tag. However, the mixed case still falls through to the existing comma-list tag serializer, so tag: [none, Test] becomes tag:none,Test (a literal tag named none), which is incorrect.
This mixed behavior is not newly introduced by #91652 — before that PR, all selected tag values were serialized through the same generic tag:<comma-list> path, so No tag + Test was already represented as tag:none,Test.
For comparison, the Category filter has a similar empty-value option but works because "No category" lives inside the category: value domain (e.g. category:none,Uncategorized,<real category>). Tag behaves differently because the correct representation for missing tags is -has:tag, a different filter key/operator from tag:<tag name>.
Why this is non-trivial
A complete fix for the mixed tag case would likely need either:
- search query/query-language support for an OR expression like
(-has:tag) OR tag:Test, or - explicit support for treating
tag:none,Testas "missing tag OR tag Test".
A client-only workaround like emitting -has:tag tag:Test would be incorrect because separate filters are combined as AND, so it would mean "has no tag AND tag is Test". Running two searches and merging client-side would also be risky because it could break pagination, totals, hashes, bulk actions, and select-all-matching behavior.
Expected behavior
Selecting No tag together with one or more real tags should return both untagged expenses and expenses matching the selected tag(s) (OR semantics), consistent with how other multi-select filters behave.
Reproduction steps
- Have at least one expense with a tag (e.g.
Test) and at least one expense with no tag. - Go to Spend > Expenses.
- Open Filters and set Tag to
No tagand an existing tag (e.g.Test). - Save and view results.
- Actual: untagged expenses and
Test-tagged expenses are not both returned. - Expected: both untagged expenses and
Test-tagged expenses are returned.
A screen recording of the behavior is in #91652 (comment)
Issue Owner
Current Issue Owner: @KJ21-ENGUpwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022062534438216369167
- Upwork Job ID: 2062534438216369167
- Last Price Increase: 2026-06-04
Activity
MelvinBot commentedon Jun 3, 2026
Follow-up to #91083 / PR #91652, requested by
Beamanatorin #91652 (comment).Beamanatoris assigned. I wasn't able to auto-assign @KJ21-ENG (GitHub only allows assigning collaborators or users who've already commented on this issue) — @KJ21-ENG, please self-assign, or someone with triage access can add you.KJ21-ENG commentedon Jun 3, 2026
🫡
KJ21-ENG commentedon Jun 3, 2026
@Beamanator Please add me here so i can start working on this and raise a PR. Thanks!
melvin-bot commentedon Jun 4, 2026
Current assignee @ShridharGoel is eligible for the External assigner, not assigning anyone new.
[-]Advanced Tag filter: mixed 'No tag' + real tag selection does not return both untagged and tagged expenses[/-][+][$250] Advanced Tag filter: mixed 'No tag' + real tag selection does not return both untagged and tagged expenses[/+]melvin-bot commentedon Jun 4, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022062534438216369167
Beamanator commentedon Jun 4, 2026
Added! @ShridharGoel @KJ21-ENG can you both please first confirm the OP of this issue is accurate before we move forward with a proposal & PR?
18 remaining items
Beamanator commentedon Jun 5, 2026
Good question — checked, and yeah you're right.
-has:tagand "No tag" inside the selector both already exist, same as Category. I believe the bug is just that App converts standalone "No tag" to-has:tagwhile Category cleanly emitscategory:nonein every case.Beamanator commentedon Jun 5, 2026
does that sound about right @ShridharGoel
KJ21-ENG commentedon Jun 5, 2026
Hmm, If collision risk with a real tag named 'none' is acceptable then this sounds right to me.
The inconsistency is that Category keeps
No categoryascategory:none, while Tag special-cases standaloneNo tagto-has:tag. To align Tag with Category, App should usetag:noneforNo tagincluding mixedNo tag + real tag, while backend/search should treattag:none,<real tags>as missing-tag OR selected tags. We should keep-has:tagsupported for backward compatibility.cc:- @Beamanator @ShridharGoel @trjExpensify
ShridharGoel commentedon Jun 5, 2026
I'm not able to repro this on staging, @KJ21-ENG can you check?
KJ21-ENG commentedon Jun 5, 2026
Doesn't seem to be reproducing on staging. 👀
cc:- @ShridharGoel @Beamanator @trjExpensify
ezyZip.12.mp4
melvin-bot commentedon Jun 9, 2026
@KJ21-ENG Whoops! This issue is 2 days overdue. Let's get this updated quick!
melvin-bot commentedon Jun 11, 2026
@KJ21-ENG Eep! 4 days overdue now. Issues have feelings too...
KJ21-ENG commentedon Jun 11, 2026
@ShridharGoel @Beamanator should we close this issue for now as it's not getting reproduced anymore? 👀
melvin-bot commentedon Jun 15, 2026
@KJ21-ENG Whoops! This issue is 2 days overdue. Let's get this updated quick!
Beamanator commentedon Jun 16, 2026
GOod call closing this out!