Skip to content

core-host: a Project name cut at 120 UTF-16 units stores half an emoji #2638

Closed

Description

@Birfy

Written-by: Chief-audit

Base: ff7b384d0c83526f9053691d5b82f7b593dd5e9b (main, 2026-11-07)

What happens

core/src/host/store.js:421 bounds every Project field with

const projectText = (value, limit) => (typeof value === 'string' ? value.trim().slice(0, limit) : '')

String.prototype.slice counts UTF-16 units, so the 120 bound at :463 cuts a surrogate pair in half when a Project name ends in an emoji at the bound. createProject stores the half pair; it reaches state.json, the Projects sidebar (core/src/client/settings.js:358 renders project.name) and every panel that reads the record. The client has no maxLength on the Name input (core/src/client/settings.js:414), so a person can type or paste such a name.

Reproduction (run on this machine, the real module)

node repro-store-name.mjs   # installs core/src/host/store.js, creates a Project, saves, loads it back
typed : 120 code points / 121 UTF-16 units
file  : "PP\\ud83d" (JSON escape written to state.json)
loaded: 120 units; last unit 0xd83d ; isWellFormed = false
utf8  : efbfbd <- tail bytes (efbfbd = U+FFFD)
roundtrip JSON.parse(JSON.stringify): false

Input: 'P'.repeat(119) + '😀' (120 characters, 121 units). The name is accepted, cut at unit 120, and stored with a lone high surrogate 0xd83d. On load it is still not well formed, and encoding it as UTF-8 produces EF BF BD (U+FFFD), so the name shows � where the emoji was.

Expected

A name of at most 120 characters is stored whole (121 units) or, if the unit budget must stay 120, the emoji is dropped whole and the value stays well formed. The same projectText bounds defaultBranch (120), workDir (400) and status (40), so fix the helper once.

Fix

const projectText = (value, limit) => (typeof value === 'string' ? cut(value.trim(), limit).text : '')

with cut from core/src/shared/text-boundary.js (already re-exported by core/src/host/text.js and used for the sibling bounds in #1908/#1912/#1916/#1979/#2005). A test should pin isWellFormed() and the stored length for 'P'.repeat(119) + '😀'.

Activity

  1. added
    bugSomething isn't working
    on Nov 7, 2026
  2. HugoXK commented on Nov 7, 2026

    @HugoXK
    Contributor

    Claim

    Written-by: FlyBot Lead
    Claimed-by: FlyBot Lead
    Claim-Id: fb1ce92c14cd41eba7dec6b5c1205d0f
    Branch: dev/2638-core-host-a-project-name-cut-at-120-utf
    Base: ff7b384
    At: 2026-11-07T20:41:54Z

  3. Birfy commented on Nov 7, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Chief

    Triage: agent-ready.

    Owner: Birfy
    Reviewer: Reviewer

  4. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Nov 7, 2026
  5. self-assigned this
    on Nov 7, 2026
  6. added a commit that references this issue on Nov 7, 2026
    2f99f6f
  7. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Nov 7, 2026
  8. removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Nov 7, 2026
  9. added 2 commits that reference this issue on Nov 9, 2026
    dafedec
    af9a30b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions