Skip to content

session: count framed payload lengths in characters, not bytes - #7

Merged
ale-rinaldi merged 1 commit into
mainfrom
fix/frame-length-in-characters
Sep 5, 2026
Merged

session: count framed payload lengths in characters, not bytes#7
ale-rinaldi merged 1 commit into
mainfrom
fix/frame-length-in-characters

Conversation

@ale-rinaldi

Copy link
Copy Markdown
Member

A pre-existing bug, found while validating agent management against a live account.

The bug

Every command response is framed as <id>:<length>:<payload>, and that length counts characters — the service produces it the way JavaScript's String.length and Java's String.length() do. dwshell sliced the body by bytes, which agrees only while everything is ASCII. One non-ASCII character and the payload is cut short, leaving JSON that ends mid-structure.

Captured live, a rejected agent name:

K:K11:71:K:{"message":"L'agente 'x' già esiste.","status":"error"}

71 characters, 72 bytes. dwshell took 71 bytes, lost the closing brace, and reported:

dwshell: create agent "x": parse agent commit response: unexpected end of JSON input

instead of the service's own message.

Blast radius — narrower than it looks, and worth recording

The agent escapes non-ASCII in its own responses (à), so anything coming from an agent was never affected — file listings with accented names included. I verified this: dwshell ls over a directory containing città-più-perché.txt works correctly even without this fix, because the agent sends the name escaped and the payload stays pure ASCII.

It is the account channel that truncates, where the service answers with literal UTF-8. Localized error messages are the common case, which is why this only surfaced on an Italian account.

The fix

Payload lengths are consumed in UTF-16 code units rather than Go runes: up to U+FFFF the two agree, and beyond it the service counts two, so counting runes would have traded one off-by-one for another. There is a test for each.

Verification

Before After
Rejected agent name (live) unexpected end of JSON input L'agente 'x' già esiste.
ls over accented filenames (live) works (agent escapes) works
Unit tests: character length, UTF-16 beyond the BMP pass

gofmt, go vet, go test -race ./... green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG

Each command response is framed as <id>:<length>:<payload>, and that
length counts characters — the service produces it the way JavaScript's
String.length and Java's String.length() do. dwshell sliced the body by
bytes, which agrees only while everything is ASCII: one non-ASCII
character and the payload is cut short, leaving JSON that ends
mid-structure.

Found against a live account, where a rejected agent name came back as

  K:K11:71:K:{"message":"L'agente 'x' già esiste.","status":"error"}

71 characters, 72 bytes. dwshell took 71 bytes, lost the closing brace,
and reported "unexpected end of JSON input" instead of the service's
own message.

The blast radius is narrower than it first appears, and worth recording:
the agent escapes non-ASCII in its own responses (\u00e0), so anything
coming from an agent — file listings with accented names included — was
never affected. It is the account channel, where the service answers
with literal UTF-8, that truncates. Localized error messages are the
common case, which is why this surfaced only on an Italian account.

Counted in UTF-16 code units rather than Go runes: for anything up to
U+FFFF the two agree, and beyond it the service counts two, so runes
would have traded one off-by-one for another.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
@ale-rinaldi
ale-rinaldi merged commit 9004677 into main Sep 5, 2026
1 check passed
@ale-rinaldi
ale-rinaldi deleted the fix/frame-length-in-characters branch September 5, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant