Skip to content

core-host: three limits count UTF-16 units, so emoji cost double and the refusal is wrong #1895

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 4b9a24ffdc514391bb52adc3ada11c2e
Branch: dev/1-host-limits-count-code-points
Base: 148554c
At: 2026-10-31T05:50:04Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

Claim: the Claim-Id block at the end of this body is our own team's record, not a third-party claim.

What is missing

#1886/#1888 fixed one writer of this root cause — the Project memory bound — by counting code points (core/src/host/store.js:523, core/src/host/project-assistant.js:193, and the panel counter in core/src/client/project-board.js). Three other host limits still measure UTF-16 code units, so every character outside the BMP costs two and the number printed in the refusal is wrong as well:

  • core/src/host/memory.js:38 MEMORY_LIMITS.entry = 300, compared in entryProblem (:533) as text.length > MEMORY_LIMITS.entry, and the refusal at :534 prints text.length as the entry's size; the file-size accounting at :544/:546 and sizeNote (:553) also use .length.
  • core/src/host/schedules.js:38 MAX_TITLE = 120, compared at :338 as title.length > MAX_TITLE.
  • core/src/host/hooks.js:37 MAX_SEND_TEXT = 20_000, compared at :731 as text.length > MAX_SEND_TEXT.

So a fact of 151 emoji is 151 characters to a reader but 302 to entryProblem, and is refused by a 300-character limit as An entry holds at most 300 characters; this one has 302.

Reproduction (live main 148554c1)

The unit mismatch itself, on this machine:

$ node -e 'const s="\u{1F600}".repeat(151); console.log(s.length, [...s].length)'
302 151
$ node -e 'const s="\u{1F600}".repeat(61); console.log(s.length, [...s].length)'
122 61
$ node -e 'const s="\u{1F600}".repeat(10001); console.log(s.length, [...s].length)'
20002 10001

Each of the three sites compares the first number against a limit written for the second:

  • memory.js:533 — 151 emoji (302 units) is refused against 300, though it is 151 characters.
  • schedules.js:338 — a 61-emoji title (122 units) is refused against 120, though it is 61 characters.
  • hooks.js:731 — a 10,001-emoji message (20002 units) is refused against 20_000, though it is 10,001 characters.

Expected

  • All of these sites count code points, so one character costs one whatever its UTF-16 width, and any number printed in a refusal is the code-point count.
  • One shared helper exported once (e.g. codePoints(text) returning [...text].length) and used by memory.js, schedules.js, hooks.js and the two writers #1888 already fixed, so the next limit cannot pick the wrong unit.
  • Tests pin both directions per limit: a string exactly at the limit in code points is accepted, the same string one character longer is refused with the code-point number; at least one case uses emoji, not only ASCII.

Scope

Host limits only: core/src/host/memory.js, core/src/host/schedules.js, core/src/host/hooks.js, the helper's home, and matching tests under core/test/. The client needs no change (the panel counter already counts code points), and the limit values stay as they are — 300 / 4,000 / 16,000 / 120 / 20,000.

Duplicate check (live main 148554c1 + closed issues): #1886, #1888, #1887, #1889 cover only the Project memory writers and the panel counter; no issue covers these three host sites.

Activity

  1. added a commit that references this issue on Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions