Repository navigation
core-host: the remaining code-unit cuts (claim cue, read_own_chat query/around, page title, diagnostics lines, peer name) #1916
Description
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: 243cd3afc769474d8eaa02a692835b71
Branch: dev/2-p275-residual-code-unit-cuts
Base: 4486660
At: 2026-10-31T09:10:11Z
#1912 (PR #1915, merged in 44866600) gave the host one exported cut(text, max) in core/src/host/text.js and moved six writers onto it. Reviewing that change turned up nine more code-unit cuts on the same paths, none of them touched by #1912, each able to store or emit half a surrogate pair. The table below has eight rows because its diagnostics.js row covers two cuts (:211 and :220): nine cuts, eight rows — PR bodies must say nine.
What and where (live main 44866600)
| file:line | cut | why it matters |
|---|---|---|
core/src/host/groups.js claimCueText |
title.slice(0, CLAIM_CUE_REF_MAX) (60) |
a title with no #1234 prefix is the claim line's reference; a pair split at 60 puts an ill-formed string into the room notice and the room log |
core/src/host/groups.js:964 |
String(args.query ?? '').trim().slice(0, 80) |
the read_own_chat query is stored in the room log and used for matching, so the character is also unsearchable |
core/src/host/groups.js:965 |
String(args.around ?? '').trim().slice(0, 40) |
the same, for the anchor |
core/src/host/browser.js:254 |
page.url.slice(0, 2000), String(page.title ?? '').slice(0, 300) |
the page title/url a Bot reads back, from outside the team |
core/src/host/diagnostics.js:211, :220 |
clean(value.slice(0, max)), clean(item.text.slice(0, 500)), clean(item.code.slice(0, 40)) |
browser-log lines in the diagnostics report |
core/src/host/groups.js:330 |
String(rt.botOf(leadId)?.name ?? 'The task lead').replace(/[\r\n]+/g, ' ').slice(0, 80) |
the room's task-lead name goes into every member's system prompt; a Bot name is owner-set and may carry an emoji |
core/src/host/groups.js:722 |
String(error?.message ?? error).slice(0, 300) |
a failed turn's error text lands in the room notice and the room log |
core/src/shared/remote-label.js:6 |
cleanPeerName → .slice(0, 60) |
a friend's Bot or room name, used as a display label |
Reproduce
read_own_chat { query: 'a'.repeat(79) + '😀permalink' } on 44866600 writes \ud83d into state.json and an ill-formed lookup line into the room log; the same string is used for includes/FTS matching, so the emoji is neither stored nor findable. claimCueText({ title: 'a'.repeat(59) + '😀tail' }) returns a cue ending in a lone high surrogate. cleanPeerName('b'.repeat(59) + '😀tail') returns 59 units plus a lone surrogate, which then flows into cleanLabel etc.
Expected
Every one of these goes through cut(...) so the prefix never splits a pair, with the limits unchanged (still UTF-16 units, values at or under the old bound). For remote-label.js the helper lives in host/, which shared/ must not import: move the boundary rule to a shared module (e.g. core/src/shared/text-boundary.js) and have host/text.js build cut on it, so both layers use one implementation.
Red first
groups:claimCueTextwith'a'.repeat(59) + '😀tail'→isWellFormed() === true, ref is the 59-unit prefix;read_own_chatwith'a'.repeat(79) + '😀permalink'→ log string well-formed, ≤ 80, and a byte-for-byte prefix of the input after dropping the tail; one case foraroundat 40.browser: open-page title/url at the boundary stay well-formed.diagnostics: a browser-log line at the boundary stays well-formed.remote-label:cleanPeerNameat 60 stays well-formed.
Out of scope
Input that is already ill-formed (a tool argument or a friend's name carrying a lone surrogate) is a separate, existing shape: cut is a prefix operation and cannot repair the middle of a string. read_own_chat's fixed 20/12 caps are also separate (P-274).
Duplicate check
Searched the live tree for slice(0 in core/src/host and core/src/shared, and the open/closed issues for U+FFFD, surrogate, cleanPeerName, read_own_chat query, claimCueText: the only closed matches are #1907/#1908 (clip/excerpt, merged), #1912 (the six writers, merged in 44866600), and unrelated naming changes (#1151, #502, #541). Nothing open covers these six lines.
Record: the #1915 changelog
changelog.d/1912-archive-cut-boundary.fixed.md (merged with #1915, now on main) ends "… and the claim line, diagnostics report, activity-feed label and browser text beside them — so the stored record round-trips through UTF-8 as the whole character instead of U+FFFD". That sentence only becomes true once this issue lands: claimCueText's fallback ref (groups.js) still cuts by code unit on main. Do not edit the merged changelog — the PR body for this issue must carry one line: The #1915 changelog sentence about the claim line only holds once this change lands. so the audit can line the two up.
Corrections from the audit (measured on 44866600)
- Line numbers above were taken from an older tree; on
44866600they aregroups.js:51(claim cueref),:964/:965(query/around),:330(task-lead name) and:722(failed-turn error text),browser.js:254,diagnostics.js:211/:220. - Two more sites of the same family belong here:
groups.js:330andgroups.js:722(both added to the table above). - Red-first assertions must not require an exact length:
cut(text, max)returnsmax - 1units when the pair straddles the limit. Assertlength <= max,isWellFormed() === true, and that dropping the tail gives a byte-for-byte UTF-8 prefix of the input.
Not in this change (next round)
core/src/host/meetings.js:30 slugOf's trailing .slice(0, 80): reachable only when toLowerCase() expands (İ → i̇) and the slug then ends on an astral \p{L} letter (Deseret and similar); it only affects the archive file name. No runtime reproduction was produced for it, and the scope of this issue is frozen at the eight sites above — it goes into the next round instead of this pull request.
Activity
host: cut the last code-unit bounds on a code point boundary (ThinkFl…
HugoXK commentedon Oct 31, 2026
Release
Written-by: FlyBot Lead
Released: 243cd3afc769474d8eaa02a692835b71
Reason: stood down
At: 2026-10-31T10:15:19Z
diagnostics: the stack-summary test pins only the upper bound, so a w…
host: cut the memory review's text on a code point boundary and make …
room-share: cut the cross-owner body and history lines on code point …
host: bound a group notice and name on a code point, not a UTF-16 unit