Skip to content

fix(go-adk): truncate A2A session name on rune boundaries#2234

Open
anxkhn wants to merge 1 commit into
kagent-dev:mainfrom
anxkhn:patch-10
Open

fix(go-adk): truncate A2A session name on rune boundaries#2234
anxkhn wants to merge 1 commit into
kagent-dev:mainfrom
anxkhn:patch-10

Conversation

@anxkhn

@anxkhn anxkhn commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

extractSessionName in go/adk/pkg/a2a/executor.go derives a session name from the
first text part of an inbound A2A message and caps it at sessionNameMaxLength (20).
The cap used a byte-wise slice:

if len(tp.Text) > sessionNameMaxLength {
    return tp.Text[:sessionNameMaxLength] + "..."
}

Both len() and the [:20] slice operate on bytes, not runes. A first message longer
than 20 bytes that begins with multi-byte UTF-8 (CJK, emoji, accented Latin) gets cut
in the middle of a rune, producing an invalid-UTF-8 string.

This is only ~6-7 non-ASCII characters, so it is easy to hit for non-English users.
For example こんにちは世界 (7 CJK runes = 21 bytes) truncates to
こんにちは世\xe7\x95..., which is not valid UTF-8.

Why it matters

The derived name flows into state[StateKeySessionName] and is persisted via
CreateSession:

  • On the Postgres session store, a text column rejects invalid UTF-8, so the
    session-create call fails and the whole turn is aborted with an error, a valid
    non-English first message can break the conversation.
  • On SQLite, the invalid bytes are stored as mojibake and shown in the UI.

The fix

Count the cap in runes and slice on a rune boundary, so the result is always valid
UTF-8:

if utf8.RuneCountInString(tp.Text) > sessionNameMaxLength {
    return string([]rune(tp.Text)[:sessionNameMaxLength]) + "..."
}

Pure-ASCII behavior is unchanged (for ASCII, byte count == rune count), and the "..."
suffix is kept. The only additions are the unicode/utf8 import and a short doc comment
explaining why truncation is rune-aware.

Tests

Adds a table-driven TestExtractSessionName colocated in
go/adk/pkg/a2a/executor_test.go covering:

  • nil message / no parts / short ASCII (unchanged behavior)
  • ASCII exactly at the 20-rune limit (not truncated)
  • ASCII over the limit (truncated with ...)
  • multi-byte over the 20-byte limit but under the rune limit (must stay valid UTF-8)
  • long multi-byte truncated on a rune boundary
  • skipping an empty text part and using the first non-empty one

Each case asserts both the exact result and utf8.ValidString(got). The multi-byte
cases fail against the old byte-slice code (they return invalid UTF-8) and pass with the
fix; the ASCII cases pass either way.

Verified locally:

cd go && go test -race -run TestExtractSessionName -v ./adk/pkg/a2a/   # PASS (8/8)
cd go && go test -race -skip 'TestE2E.*' ./adk/...                     # ok, no regressions
cd go && go build ./adk/... && go vet ./adk/pkg/a2a/                   # clean

This is a small (<100 LoC) bug fix with unit coverage and no CRD/API change, so no E2E
change is needed.

extractSessionName derived the session name from the first text part of an
inbound A2A message and capped it with a byte-wise slice
(tp.Text[:sessionNameMaxLength]). Because len() and the slice operate on
bytes, a first message longer than the 20-byte cap that begins with
multi-byte UTF-8 (CJK, emoji, accented Latin) was cut in the middle of a
rune, yielding an invalid-UTF-8 name. That value is persisted via
CreateSession: Postgres rejects the invalid text and fails the turn, while
SQLite stores mojibake.

Count the cap in runes and slice on a rune boundary so the result is always
valid UTF-8. Pure-ASCII behavior is unchanged. Adds a table-driven test
covering ASCII, at-limit, and multi-byte truncation.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 14, 2026 00:17
@anxkhn anxkhn requested a review from supreme-gg-gg as a code owner July 14, 2026 00:17
@github-actions github-actions Bot added the bug Something isn't working label Jul 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes UTF-8 corruption in A2A session name derivation by ensuring truncation happens on rune boundaries (preventing invalid UTF-8 that can break Postgres session creation), and adds focused unit tests to lock in correct behavior for ASCII and multibyte inputs.

Changes:

  • Update extractSessionName to truncate based on rune count rather than byte count.
  • Add a table-driven TestExtractSessionName validating exact outputs and utf8.ValidString(...) across ASCII + multibyte scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
go/adk/pkg/a2a/executor.go Makes session-name truncation UTF-8 rune-aware to avoid invalid strings on multibyte input.
go/adk/pkg/a2a/executor_test.go Adds unit coverage ensuring truncation correctness and UTF-8 validity for multibyte and ASCII cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +437 to 440
if utf8.RuneCountInString(tp.Text) > sessionNameMaxLength {
return string([]rune(tp.Text)[:sessionNameMaxLength]) + "..."
}
return tp.Text
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants