You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] Compose Box - Maximum comment length error when a link is pasted in the composer #102500
Version Number: v9.4.95-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:#100421 Email or phone of affected tester (no customers):agexptest+pr280926@gmail.com Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: Windows 11/ Chrome, Samsung S23FE/ Android 16, iPhone 13 Mini/ iOS 26.5 App Component: Chat Report View
changed the title [-]Compose Box-Maximum comment length error when a link is pasted in the composer[/-][+]Compose Box - Maximum comment length error when a link is pasted in the composer[/+]on Sep 29, 2026
The length check counts the message's HTML, not the characters you see. ExpensiMark turns the pasted string into a link, so the text appears twice in the HTML (<a href="https://aaa…com" …>aaa…com</a>). The 8,500 visible characters become 17,065 HTML characters, which is over the 15,000 limit.
getCommentLength measures the parsed HTML on purpose, so the App matches how the backend counts AddComment length.
The real bug is in expensify-common. URL_WEBSITE_REGEX accepts hostname labels of any length ([-a-z0-9]*). DNS allows at most 63 characters per label, so aaa…(8,496)….com is not a real domain and should never become a link.
Typing still works because the input's hard cap counts raw characters (MAX_COMMENT_LENGTH + 1). The HTML check only blocks sending.
What changes do you think we should make in order to solve the problem?
Change expensify-common, then bump it in App:
In URL_WEBSITE_REGEX, limit each hostname label to 63 characters ([-a-z0-9]{0,61} in place of [-a-z0-9]*).
Add a lookbehind so a match can't start in the middle of a label ((?<![-a-z0-9.])). Without it, the regex links just the last 63 characters of the string.
Apply the same 63-character rule in the new candidate scanner (isValidHostnameLabel / findHostnameStart in ExpensiMark.ts) so both paths agree.
Add unit tests: a 63-character label links, a 64-character label does not, and common URLs still link.
With these changes, the pasted string stays plain text and counts as 8,500 characters, so no error appears.
What alternative solutions did you explore? (Optional)
Count raw characters in App. This breaks parity with the backend. The HTML count was added on purpose to stop AddComment from failing.
Override the autolink rule in App's Parser.ts. This is a workaround, and the composer's live Markdown highlighting would still show the link.
Investigation details
The researcher ran the real getCommentLength in a Jest test on App 68ad1ed:
Input
getCommentLength
'a'.repeat(8496) + '.com'
17,065 (over 15,000)
'a'.repeat(8500)
8,500
Same output from expensify-common 2.0.204 and 2.0.207
Yes
The link adds 65 characters plus a second copy of the URL: HTML length = 2 × length + 65. Any bare "domain" of 7,468 or more characters triggers the error.
Text over MAX_MARKUP_LENGTH (10,000) is not parsed (ReportUtils.ts:6933). So the same string at 10,001 or more characters does not show the error.
We tested the 63-character cap plus lookbehind by overriding the regex in Jest. The 8,500-character string was not linked. google.com, https://www.expensify.com/path?x=1, sub.domain.example.co.uk, test@expensify.com, and _expensify.com_ still behaved as before.
A live web reproduction didn't finish because the test session's page went blank. The Jest run above calls the same code the composer uses.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/102500) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Compose Box - Maximum comment length error when a link is pasted in the composer[/-][+][$175] Compose Box - Maximum comment length error when a link is pasted in the composer[/+]on Sep 29, 2026
Please re-state the problem that we are trying to solve in this issue.
When pasting an 8,500-character string formatted like a pseudo-domain (e.g. 'a'.repeat(8496) + '.com') into the composer, the "Maximum comment length exceeded" error message is prematurely displayed even though the raw text is well within the 10,000 character limit.
What is the root cause of that problem?
In src/libs/ReportUtils.ts:10798-10802, getCommentLength computes comment length on the parsed HTML output via ExpensiMark (rather than raw plaintext characters) to ensure parity with the backend auth validator for AddComment:
When the user pastes 'a'.repeat(8496) + '.com', ExpensiMark in expensify-common applies URL_WEBSITE_REGEX ([-a-z0-9]*\.[a-z]{2,}).
Under RFC 1035 and DNS specifications, a valid domain label cannot exceed 63 characters. However, the current regex in expensify-common allows unlimited characters in the label ([-a-z0-9]*).
Because the 8,496-character string is incorrectly recognized as a URL, ExpensiMark autolinks it, duplicating the string in both href and inner text: <a href="https://aaaa...com" target="_blank" rel="noreferrer noopener">aaaa...com</a>
This balloons the 8,500 visible characters into 17,065 HTML characters, which exceeds the backend and client limit (15,000 HTML characters / CONST.MAX_COMMENT_LENGTH), triggering useDebouncedCommentMaxLengthValidation.ts:38-41 to flag a maximum length violation.
What changes do you think we should make in order to solve the problem?
Enforce RFC 1035 63-Character Label Limit in expensify-common (and ExpensiMark.ts):
Update URL_WEBSITE_REGEX in expensify-common/lib/Url.js (and src/libs/ExpensiMark.ts) to restrict domain label length to a maximum of 63 characters:
Replace [-a-z0-9]* with [-a-z0-9]{1,63} and include boundary checks:
In ExpensiMark.ts, enforce label.length <= 63 in hostname label validation (isValidHostnameLabel).
Result:
Any string with an unsegmented label exceeding 63 characters (such as a.repeat(8496) + '.com') will not be treated as a website link.
It remains plain text of 8,500 characters, well within the limit, preventing the false "maximum comment length exceeded" error.
Add Unit Tests in tests/unit/ExpensiMarkTest.ts:
Verify that domain labels <= 63 characters are correctly autolinked.
Verify that domain labels > 63 characters remain plain text and do not inflate HTML length.
What alternative solutions did you explore? (Optional)
Calculating comment length purely on raw text in the client: Explored and rejected because if a message containing legitimate Markdown/links exceeds the backend 15,000 HTML limit, the server would reject the AddComment command with an error. Enforcing the RFC 63-character hostname standard in ExpensiMark fixes the root autolinking defect while maintaining full client-backend length parity.
This issue was found during QA for our PR: #100421, and I already faced this issue locally while solving the long .comx and heavy dotted URL case for ExpensiMark follow up PR: Expensify/expensify-common#945. Since it isn't a regression introduced by our PR, solving this issue also includes updating the candidate scanning logic introduced by our ExpensiMark performance PR: Expensify/expensify-common#937
Since we already have the full context and idea for these parser changes, happy to be assigned. Thank you!
Status: The root cause is still the one in my earlier proposal. ExpensiMark autolinks hostnames with labels over 63 characters, so the HTML doubles to 17,065 characters. fix: enforce DNS label and hostname length limits in ExpensiMark fixes the repro. It also adds 3 regressions that the review threads haven't covered yet. I tested head 111ae2e.
Repro fixed:'a'.repeat(8496) + '.com' now stays plain text. Its HTML length drops from 17,065 to 8,500. All 702 tests pass, and typecheck and lint are clean.
New regressions to fix before merge:
A valid domain after dots no longer links. In these examples, google.com or b.com links on main but stays plain on the PR:
I checked...google.com
wait..google.com
a..b.com
example.com..google.com
findRawHostnameStart walks back over the dots, so the whole run counts as one invalid hostname. analyzeUrlRange can recover a valid prefix but not a valid suffix. The new lookbehind in Url.js:10 blocks the same match. This is the leading-side version of the example.com..nonsense case that is already fixed.
A valid prefix is lost when a long word follows it. In google.com. + b×64 and example.com- + b×70, google.com and example.com link on main but nothing links on the PR. The length check at ExpensiMark.ts:366-368 returns without the valid-prefix end it already found. This contradicts the PR's own example.com-thanks test.
Quadratic slowdown on URLs with dotted paths. For 'https://x.com/' + 'a.co/'.repeat(n), the median parse time is:
Length
main
PR
2.5k chars
0.10 ms
1.2 ms
5k chars
0.17 ms
4.3 ms
10k chars
0.30 ms
17.6 ms
After a valid URL, findInvalidUrlRanges resumes at the end of the hostname (L524) or TLD (L540), not the end of the URL. Each later .co then calls extendUrlCandidateBoundaries, which scans to the next space. Resuming at the end of the URL should fix it.
Needs a decision (outside the agreed scope):
[text](https://<64-char label>.com) and the matching image markdown now show as raw markdown. Main renders them as a link and an image.
Main linked the valid tail of hostnames with bad label edges, such as bad-.example.com, foo_.example.com and https://-bad.example.com. The PR leaves them plain. That's defensible, but https://bad-.example.com still partly links https://bad, which is inconsistent.
Investigation details
I compared new ExpensiMark().replace() output on main (a61b5b4) and the PR for about 160 inputs. 34 outputs differ. Most are intended: 64-character labels and 254-character hostnames now stay plain. 63-character labels and 253-character hostnames still link.
These cases are unchanged from main:
Ports, query strings, fragments and trailing punctuation
Punycode, IPv4, www. and co.uk
Emails, mentions and #room
Inline code and code blocks
Long paths
Inputs around MAX_MARKUP_LENGTH
htmlToMarkdown and htmlToText round-trips
Cases from earlier review comments are fixed and match main: example.com..nonsense, example.com.Thanks! and google.com....
Other timings: the repro and 'a.'.repeat(n)+'com' are the same speed or faster on the PR. Comma-separated domain lists were already super-linear on main, and the PR makes them 14–29% slower. A typical chat message takes 0.157 ms on main and 0.188 ms on the PR.
I tested with Jest/node against the library directly. I didn't do a live web test, because App needs a dependency bump first.
Next Steps:Uzaifm127 can address items 1–3 in the expensify-common PR. Reply with @MelvinBot <your feedback> to refine this analysis.
Activity
applause-bot commentedon Sep 29, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/1dfad893b0feb7d2629ca1f27f01fe7c0b49d24b419b4994a903b58eecf58042
[-]Compose Box-Maximum comment length error when a link is pasted in the composer[/-][+]Compose Box - Maximum comment length error when a link is pasted in the composer[/+]MelvinBot commentedon Sep 29, 2026
Proposal
What is the root cause of that problem?
The length check counts the message's HTML, not the characters you see. ExpensiMark turns the pasted string into a link, so the text appears twice in the HTML (
<a href="https://aaa…com" …>aaa…com</a>). The 8,500 visible characters become 17,065 HTML characters, which is over the 15,000 limit.getCommentLengthmeasures the parsed HTML on purpose, so the App matches how the backend countsAddCommentlength.URL_WEBSITE_REGEXaccepts hostname labels of any length ([-a-z0-9]*). DNS allows at most 63 characters per label, soaaa…(8,496)….comis not a real domain and should never become a link.Typing still works because the input's hard cap counts raw characters (
MAX_COMMENT_LENGTH + 1). The HTML check only blocks sending.This is not a regression from fix: preserve Markdown formatting in long messages. expensify-common 2.0.204, the version before that PR, gives the same 17,065-character output.
What changes do you think we should make in order to solve the problem?
Change expensify-common, then bump it in App:
URL_WEBSITE_REGEX, limit each hostname label to 63 characters ([-a-z0-9]{0,61}in place of[-a-z0-9]*).(?<![-a-z0-9.])). Without it, the regex links just the last 63 characters of the string.isValidHostnameLabel/findHostnameStartinExpensiMark.ts) so both paths agree.With these changes, the pasted string stays plain text and counts as 8,500 characters, so no error appears.
What alternative solutions did you explore? (Optional)
AddCommentfrom failing.Parser.ts. This is a workaround, and the composer's live Markdown highlighting would still show the link.Investigation details
The researcher ran the real
getCommentLengthin a Jest test on App68ad1ed:getCommentLength'a'.repeat(8496) + '.com''a'.repeat(8500)MAX_MARKUP_LENGTH(10,000) is not parsed (ReportUtils.ts:6933). So the same string at 10,001 or more characters does not show the error.useDebouncedCommentMaxLengthValidation.ts:38).google.com,https://www.expensify.com/path?x=1,sub.domain.example.co.uk,test@expensify.com, and_expensify.com_still behaved as before.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102500)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
melvin-bot commentedon Sep 29, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @thelullabyy (
External)[-]Compose Box - Maximum comment length error when a link is pasted in the composer[/-][+][$175] Compose Box - Maximum comment length error when a link is pasted in the composer[/+]melvin-bot commentedon Sep 29, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022104830733949161960
bonnhatnguyen commentedon Sep 29, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When pasting an 8,500-character string formatted like a pseudo-domain (e.g.
'a'.repeat(8496) + '.com') into the composer, the "Maximum comment length exceeded" error message is prematurely displayed even though the raw text is well within the 10,000 character limit.What is the root cause of that problem?
src/libs/ReportUtils.ts:10798-10802,getCommentLengthcomputes comment length on the parsed HTML output viaExpensiMark(rather than raw plaintext characters) to ensure parity with the backend auth validator forAddComment:'a'.repeat(8496) + '.com',ExpensiMarkinexpensify-commonappliesURL_WEBSITE_REGEX([-a-z0-9]*\.[a-z]{2,}).expensify-commonallows unlimited characters in the label ([-a-z0-9]*).ExpensiMarkautolinks it, duplicating the string in bothhrefand inner text:<a href="https://aaaa...com" target="_blank" rel="noreferrer noopener">aaaa...com</a>CONST.MAX_COMMENT_LENGTH), triggeringuseDebouncedCommentMaxLengthValidation.ts:38-41to flag a maximum length violation.What changes do you think we should make in order to solve the problem?
expensify-common(andExpensiMark.ts):URL_WEBSITE_REGEXinexpensify-common/lib/Url.js(andsrc/libs/ExpensiMark.ts) to restrict domain label length to a maximum of 63 characters:Replace
[-a-z0-9]*with[-a-z0-9]{1,63}and include boundary checks:ExpensiMark.ts, enforcelabel.length <= 63in hostname label validation (isValidHostnameLabel).a.repeat(8496) + '.com') will not be treated as a website link.tests/unit/ExpensiMarkTest.ts:What alternative solutions did you explore? (Optional)
AddCommentcommand with an error. Enforcing the RFC 63-character hostname standard inExpensiMarkfixes the root autolinking defect while maintaining full client-backend length parity.Contributor details
Your Expensify account email: bonnhatnguyen0@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0139ed4098bdc50182
melvin-bot commentedon Sep 29, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
Uzaifm127 commentedon Sep 29, 2026
@JS00001
Can me and @linhvovan29546 work on this issue? Because
This issue was found during QA for our PR: #100421, and I already faced this issue locally while solving the long
.comxand heavy dotted URL case forExpensiMarkfollow up PR: Expensify/expensify-common#945. Since it isn't a regression introduced by our PR, solving this issue also includes updating the candidate scanning logic introduced by our ExpensiMark performance PR: Expensify/expensify-common#937Since we already have the full context and idea for these parser changes, happy to be assigned. Thank you!
JS00001 commentedon Sep 29, 2026
Works for me
21 remaining items
Uzaifm127 commentedon Oct 13, 2026
Not overdue.
PR is WIP.
linhvovan29546 commentedon Oct 13, 2026
Not overdue. I’m reviewing the PR, so add the Weekly label.
MelvinBot commentedon Oct 13, 2026
Issue Analysis
Status: The root cause is still the one in my earlier proposal. ExpensiMark autolinks hostnames with labels over 63 characters, so the HTML doubles to 17,065 characters. fix: enforce DNS label and hostname length limits in ExpensiMark fixes the repro. It also adds 3 regressions that the review threads haven't covered yet. I tested head
111ae2e.Repro fixed:
'a'.repeat(8496) + '.com'now stays plain text. Its HTML length drops from 17,065 to 8,500. All 702 tests pass, and typecheck and lint are clean.New regressions to fix before merge:
A valid domain after dots no longer links. In these examples,
google.comorb.comlinks on main but stays plain on the PR:I checked...google.comwait..google.coma..b.comexample.com..google.comfindRawHostnameStartwalks back over the dots, so the whole run counts as one invalid hostname.analyzeUrlRangecan recover a valid prefix but not a valid suffix. The new lookbehind inUrl.js:10blocks the same match. This is the leading-side version of theexample.com..nonsensecase that is already fixed.A valid prefix is lost when a long word follows it. In
google.com.+b×64 andexample.com-+b×70,google.comandexample.comlink on main but nothing links on the PR. The length check atExpensiMark.ts:366-368returns without the valid-prefix end it already found. This contradicts the PR's ownexample.com-thankstest.Quadratic slowdown on URLs with dotted paths. For
'https://x.com/' + 'a.co/'.repeat(n), the median parse time is:After a valid URL,
findInvalidUrlRangesresumes at the end of the hostname (L524) or TLD (L540), not the end of the URL. Each later.cothen callsextendUrlCandidateBoundaries, which scans to the next space. Resuming at the end of the URL should fix it.Needs a decision (outside the agreed scope):
[text](https://<64-char label>.com)and the matching image markdown now show as raw markdown. Main renders them as a link and an image.bad-.example.com,foo_.example.comandhttps://-bad.example.com. The PR leaves them plain. That's defensible, buthttps://bad-.example.comstill partly linkshttps://bad, which is inconsistent.Investigation details
new ExpensiMark().replace()output on main (a61b5b4) and the PR for about 160 inputs. 34 outputs differ. Most are intended: 64-character labels and 254-character hostnames now stay plain. 63-character labels and 253-character hostnames still link.www.andco.uk#roomMAX_MARKUP_LENGTHhtmlToMarkdownandhtmlToTextround-tripsexample.com..nonsense,example.com.Thanks!andgoogle.com....'a.'.repeat(n)+'com'are the same speed or faster on the PR. Comma-separated domain lists were already super-linear on main, and the PR makes them 14–29% slower. A typical chat message takes 0.157 ms on main and 0.188 ms on the PR.Next Steps:
Uzaifm127can address items 1–3 in the expensify-common PR. Reply with@MelvinBot <your feedback>to refine this analysis.view run
Uzaifm127 commentedon Oct 21, 2026
Update:
expensify-commonPR has been merged 🎉I'll open the version bump PR in the App tomorrow (IST).
TY!
Uzaifm127 commentedon Oct 22, 2026
Update: Draft PR is up.
PR is WIP. Thanks.
Uzaifm127 commentedon Oct 23, 2026
@linhvovan29546 The upgrade PR is ready for review, Thanks!