Skip to content

fix(qqbot): truncate reminder job name on a code-point boundary#96575

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
WeeLi-009:fix/qqbot-jobname-surrogate
Jun 27, 2026
Merged

fix(qqbot): truncate reminder job name on a code-point boundary#96575
vincentkoc merged 1 commit into
openclaw:mainfrom
WeeLi-009:fix/qqbot-jobname-surrogate

Conversation

@WeeLi-009

@WeeLi-009 WeeLi-009 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary: The QQBot reminder job name was sliced to 20 UTF-16 units, tearing an emoji at the boundary into a lone surrogate. This truncates on a code-point boundary so multi-unit characters stay intact.

What Problem This Solves

QQBot reminder job names were truncated with a raw UTF-16 .slice() budget. Inputs that place an emoji or other astral character on the truncation boundary can leave a lone surrogate in the generated job name.

Trigger example:

xxxxxxxxxxxxxxxxxxx😀tail

The first 20 UTF-16 units contain 19 ASCII characters plus only the emoji high surrogate, producing a malformed string such as:

Reminder: xxxxxxxxxxxxxxxxxxx\ud83d…

Fix

extensions/qqbot/src/engine/tools/remind-logic.ts now builds reminder job names with the shared truncateUtf16Safe helper instead of raw .slice() truncation.

That keeps the existing 20 UTF-16-unit job-name budget and ellipsis behavior, while relying on the shared helper's range validation to avoid cutting between surrogate pairs. The generated job name remains escaped/serializable as a well-formed JavaScript string even when the input contains emoji at the boundary.

Evidence

Command run from a temporary worktree checked out at fix/qqbot-jobname-surrogate:

node --import tsx demo.mts

The demo imports the real generateJobName function from extensions/qqbot/src/engine/tools/remind-logic.ts, reproduces the old raw .slice(0, 20) behavior, then calls the fixed function.

INPUT
content="xxxxxxxxxxxxxxxxxxx😀tail"
utf16_length=25

BEFORE old raw slice(0, 20)
output="Reminder: xxxxxxxxxxxxxxxxxxx\ud83d…"
boundary_char_code=0xd83d
boundary_code_point=0xd83d
lone_surrogate=true
string_from_boundary=�

AFTER generateJobName from remind-logic.ts
output="Reminder: xxxxxxxxxxxxxxxxxxx…"
lone_surrogate=false
utf16_length=30

The old path leaves a lone high surrogate (0xd83d). The fixed generateJobName output is well-formed and the lone-surrogate check returns false.

Tests

Vitest coverage was added for generateJobName in extensions/qqbot/src/engine/tools/remind-logic.test.ts.

The tests cover:

  • short reminder content remaining unchanged;
  • long ASCII content preserving the existing 20 UTF-16-unit truncation plus ellipsis behavior;
  • exactly-fitting emoji content staying unchanged;
  • emoji/surrogate-boundary truncation dropping the whole split pair and producing no lone surrogate.

@openclaw-barnacle openclaw-barnacle Bot added channel: qqbot size: XS triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 24, 2026
@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 2:07 AM ET / 06:07 UTC.

Summary
The branch changes QQBot reminder job-name generation to use truncateUtf16Safe and adds tests for ASCII, emoji, and surrogate-boundary truncation.

PR surface: Source +1, Tests +34. Total +35 across 2 files.

Reproducibility: yes. at source level: current main slices the trimmed reminder content at UTF-16 unit 20, so 19 ASCII units followed by an emoji can leave a lone high surrogate before the ellipsis. The PR body also includes terminal proof from the real generateJobName path showing the old lone surrogate and the fixed output.

Review metrics: 1 noteworthy metric.

  • Default-name reminder builders: 2 covered. Both one-shot and recurring QQBot reminder builders share generateJobName, so one helper change covers the default job-name paths before merge.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • No ClawSweeper repair is needed; the remaining action is normal maintainer review and merge handling for the existing PR.

Security
Cleared: The diff only changes QQBot string truncation and colocated tests, with no secrets, auth, dependency, CI, packaging, or code-execution surface changes.

Review details

Best possible solution:

Land the helper-based QQBot reminder fix after normal maintainer approval, keeping the shared SDK text helper as the single truncation implementation.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main slices the trimmed reminder content at UTF-16 unit 20, so 19 ASCII units followed by an emoji can leave a lone high surrogate before the ellipsis. The PR body also includes terminal proof from the real generateJobName path showing the old lone surrogate and the fixed output.

Is this the best way to solve the issue?

Yes. Reusing the existing truncateUtf16Safe SDK helper is the narrowest maintainable fix; a QQBot-local surrogate guard would duplicate shared text-boundary logic.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4fc504d321b6.

Label changes

Label justifications:

  • P2: This is a bounded QQBot reminder string-handling bug with limited blast radius and no evidence of outage, data loss, or security impact.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body provides terminal output from a demo importing the real generateJobName, showing the old raw slice produced a lone surrogate and the fixed function does not.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides terminal output from a demo importing the real generateJobName, showing the old raw slice produced a lone surrogate and the fixed function does not.
Evidence reviewed

PR surface:

Source +1, Tests +34. Total +35 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 1 39 5 +34
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 41 6 +35

What I checked:

Likely related people:

  • cxyhhhhh: Merged PR feat(qqbot): extract self-contained engine/ architecture with QR-code onboarding, approval handling #67960 added the QQBot engine architecture and the initial remind-logic.ts/test surface containing the default job-name path. (role: introduced QQBot reminder engine; confidence: high; commits: 5e72e39c1852; files: extensions/qqbot/src/engine/tools/remind-logic.ts, extensions/qqbot/src/engine/tools/remind-logic.test.ts, extensions/qqbot/src/bridge/tools/remind.ts)
  • GaosCode: Merged PR fix(qqbot): schedule reminders through cron gateway #70937 changed the same QQBot reminder engine, bridge tool, and tests while routing reminders through Gateway cron. (role: recent reminder-flow contributor; confidence: high; commits: 28497515fe1e; files: extensions/qqbot/src/engine/tools/remind-logic.ts, extensions/qqbot/src/engine/tools/remind-logic.test.ts, extensions/qqbot/src/bridge/tools/remind.ts)
  • zhang-guiping: Current-main blame attributes the present generateJobName raw-slice lines and shared UTF-16 helper lines to commit bead84f0ee6e, so they are relevant for routing current behavior questions. (role: current-main line author; confidence: medium; commits: bead84f0ee6e; files: extensions/qqbot/src/engine/tools/remind-logic.ts, src/utils.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jun 24, 2026
@WeeLi-009
WeeLi-009 force-pushed the fix/qqbot-jobname-surrogate branch from 465ec46 to b57f463 Compare June 25, 2026 00:23
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 25, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 25, 2026
generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.
@WeeLi-009
WeeLi-009 force-pushed the fix/qqbot-jobname-surrogate branch from b57f463 to b24820b Compare June 25, 2026 13:26
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 26, 2026
@vincentkoc
vincentkoc merged commit d693ed4 into openclaw:main Jun 27, 2026
103 of 105 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 28, 2026
…aw#96575)

generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…aw#96575)

generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…aw#96575)

generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 2, 2026
…aw#96575)

generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.

(cherry picked from commit d693ed4)
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…aw#96575)

generateJobName truncated reminder content with String.slice(0, 20) on
a UTF-16 code-unit index, so an astral character (e.g. an emoji) landing
on the boundary was cut into a lone surrogate, producing a malformed
cron job name.

Truncate with the shared truncateUtf16Safe helper so a surrogate pair is
never split, keeping the existing 20-unit budget and ellipsis suffix.

Adds a test asserting the truncated job name contains no lone surrogate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: qqbot P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants