Skip to content

host: the memory review's short user line and Bot line keep an unpaired surrogate in the prompt #2556

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: 8a26b27271334418813ce8a5a9f25e72
Branch: dev/5-memory-lone-surrogate
Base: 24bb90d
At: 2026-11-06T00:34:50Z

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

Written-by: FlyBot Lead

The memory review's line renderer flattens an ill-formed message only on its cut paths. A message that fits its budget is answered untouched, so an unpaired surrogate in a user line or a Bot message reaches the review prompt (and the log) as half a character.

Reproduction (live main 24bb90d1)

In core/src/host/memory.js:

  • :1281-1285 — ends(): if (fits(text, max)) return text answers the raw string; the toWellFormed() that follows only runs below that line.
  • :1294-1296 — reviewLine(): the non-user branch hands the labelled text to clip() unchanged, and clip()'s own fits answer (core/src/host/text.js:61) returns it unchanged too.

Probe, using the same render() helper the review tests already have:

  • user line 'x'.repeat(100) + '\uD800' + 'y'.repeat(100) — 201 characters, rendered line 207 (User: + 201), far inside the 3,000-character budget → { wellFormed: false, loneSurrogate: true, cut: false, replacementCount: 0 }.
  • Bot line 'z'.repeat(80) + '\uD800' + 'w'.repeat(80) — 160 characters, inside the 800-character bound → { promptWellFormed: false, loneSurrogate: true, cut: false }.

Both surrogates sit inside the part the review keeps, so this is not a pair that a cut dropped.

Expected behaviour

Flatten before bounding on both paths: the lone surrogate becomes one U+FFFD, whole and in its place, and the rest of the line and the line's length stay as they were.

  • user line: line.isWellFormed() === true, replacementCount === 1, replacementAt === 'User: '.length + 100 (106), line.slice(6, 106) === 'x'.repeat(100), line.slice(107, 207) === 'y'.repeat(100).
  • Bot line: the prompt is well formed, replacementCount === 1, and U+FFFD sits at the label's length + 80.

Scope

  • core/src/host/memory.js — ends() flattens once and answers the flattened string on both branches; reviewLine()'s non-user branch flattens the labelled text before clip().
  • core/test/memory.test.js — one test carrying both lines, red first on this issue's base.
  • docs/subsystems/core-host.md — the core/src/host/text.js row's memory-review sentence gains this rule.
  • patches/ds-bot/0001-core-fixes.patch — regenerated (memory.js and its test are inside the ds-bot + patches check).
  • changelog.d/<number>-memory-review-flattened-lines.fixed.md.

Deliberately not changed: clip() in core/src/host/text.js. It is imported by more than a dozen client/*.js modules, so flattening inside it would open the client-bundle lane; the host review flattens at its own two call sites instead.

Dedup

Searched this repository's open and closed issues and live main (24bb90d1) for surrogate and well-formed: #1907 and #1912/#1916/#1963 built the cut/codePoint* boundary rule, #1943/#1944/#1960/#1979 covered ill-formed strings arriving on other paths, #2339 and #2553 made this review's budgets count characters. None covers ends()'s fits answer or the non-user clip path, and live main has exactly one toWellFormed() in memory.js, on the cut path (:1285). The only open pull request is #2554 (steward, steward.js), which touches neither line.

Activity

  1. HugoXK commented on Nov 6, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 8a26b27271334418813ce8a5a9f25e72
    Reason: merged as c907f46 (PR #2557)
    At: 2026-11-06T01:21:18Z

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