You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Comment create returns 500 but saves the comment when an emoji sits at character 120 (activity bodySnippet splits a surrogate pair), so retries duplicate it #17103
POST /api/issues/{id}/comments returned HTTP 500, but the comment had been saved. Our client retried, and the issue ended up with three copies of the same comment. The body had an emoji (a folder pictograph inside a file path) at about character 120. Separately, PATCH /api/issues/{id} with status and comment returned 500 after saving both; we did not keep that body, so its trigger is not confirmed.
Cause, read on master 53732c8: the comment is committed first, then the route writes an activity row with bodySnippet: comment.body.slice(0, 120) (server/src/routes/issues.ts:18882 in POST /issues/:id/comments, :15361 in PATCH /issues/:id). slice counts UTF-16 code units, so when an emoji starts at index 119 the snippet ends in a lone high surrogate. JSON.stringify writes it as \ud83d, and PostgreSQL rejects an unpaired surrogate escape in the jsonb details column. logActivity throws, the request returns 500, and the comment row stays.
The same .slice(0, 120) snippet is built in 7 more places: server/src/routes/issues.ts:10657 and :17983, server/src/routes/routines.ts:315, server/src/routes/cases.ts:956, server/src/services/plugin-host-services.ts:2617, server/src/services/native-runtime/paperclip-runner-tool-authority.ts:1324, server/src/services/heartbeat/run-completion.ts:871.
Expected behavior
The comment is saved once and the call returns success. A snippet never splits a character, and a failed activity write never turns a committed write into a 500 that invites a retry.
Steps to reproduce
Take any issue you can comment on.
POST /api/issues/{id}/comments with a body of 119 ASCII characters followed by an emoji, for example "a" x 119 + "📁 rest".
The response is HTTP 500.
GET /api/issues/{id}/comments shows the comment.
Send step 2 again: a second copy is saved, again with a 500.
Paperclip version or commit
2026.1012.0 (bba6a45). The same code is on master at 53732c8.
One helper for every snippet, for example text.slice(0, n).toWellFormed() (Node 24.11 is already the minimum) or a code-point slice, used at all 9 places, plus a test with an emoji at index 119. Writing the activity row in the same transaction as the comment, or not failing the request once the comment is committed, would also stop the duplicate on retry.
Activity
joshuacox commentedon Oct 29, 2026
Working on this — a fix is in progress and a PR will follow.