Skip to content

docstring truncation splits multi-byte UTF-8 characters, producing invalid UTF-8 in nodes.properties (non-ASCII comments, e.g. Japanese) #1017

Closed

Description

@jimeng754-eng

Summary

Long docstrings/comments containing non-ASCII multi-byte UTF-8 text (observed with Japanese) can get truncated mid-character during extraction, leaving an incomplete UTF-8 byte sequence embedded in the properties JSON stored in the SQLite nodes table. This produces a properties value that is syntactically valid JSON but semantically invalid UTF-8 — any consumer that decodes the SQLite TEXT column strictly as UTF-8 (e.g. Python's sqlite3 module, which is the default for anyone building tooling on top of the exported database) throws on read.

Because this only manifests with multi-byte UTF-8 characters, it never reproduces on English-only codebases — a single-byte-per-character comment can be truncated anywhere without producing invalid UTF-8. It only shows up when indexing repos with non-ASCII comments (Japanese/Chinese/etc.).

Environment

  • codebase-memory-mcp version: 0.9.0 (Windows amd64, pre-built release binary)
  • Repro repo: EC-CUBE/ec-cube (public, MIT-licensed Japanese e-commerce platform — real-world Japanese comments throughout)
  • Indexed with mode=full

Repro steps

git clone --depth 1 https://github.com/EC-CUBE/ec-cube.git
codebase-memory-mcp cli index_repository --repo-path <path>/ec-cube --mode full --name ec-cube

Then read the nodes.properties column with any strict-UTF-8 SQLite client, e.g.:

import sqlite3
conn = sqlite3.connect("ec-cube.db")
conn.execute("SELECT id, name, file_path, properties FROM nodes WHERE label IN ('Function','Method')").fetchall()
# sqlite3.OperationalError: Could not decode to UTF-8 column 'properties' with text '...'

This one bad row aborts the entire fetchall() — not just that row — since sqlite3 decodes the whole result set as UTF-8 by default.

Observed corrupted data

Node id=5570, qualified_name="ec-cube.src.Eccube.Controller.ShoppingController.ShoppingController.index", file_path="src/Eccube/Controller/ShoppingController.php:72-151".

Raw bytes of the properties value around the break (hex):

94 9f e6 88 90 e3 82 92 e8 a1 8c e3 82 8f e3 81 9a e3 81 ab e7 22 2c 22 73 69 67 ...

Decoding what's decodable: e6 88 90=成 e3 82 92=を e8 a1 8c=行 e3 82 8f=わ e3 81 9a=ず e3 81 ab=に, then a lone e7 byte — the first byte of a 3-byte UTF-8 sequence with no continuation bytes — immediately followed by the JSON closing quote (22) and ,"signature":.... The docstring was cut exactly one byte into the next kanji character.

Scope observed

In this one EC-CUBE index run: 12 of 12,577 Function/Method nodes had this corruption (~0.1%) — small in proportion, but each one is enough to break any strict-UTF-8 batch read of the whole nodes table.

Suspected root cause (not fully confirmed — flagging both candidates)

I traced two structurally identical byte-oriented (not UTF-8-codepoint-aware) truncation loops:

  • append_json_string() in src/pipeline/pass_parallel.c (~line 401)
  • append_json_string() in src/pipeline/pass_definitions.c (~line 191, explicitly commented as "Twin of pass_parallel.c — keep both in sync")

Both advance for (const char *s = val; *s && p < bufsize - PP_ESC_MARGIN; s++) one raw byte at a time, with the truncation boundary checked purely in bytes — no awareness of where a multi-byte UTF-8 character starts/ends.

However, the surrounding code documents an "atomic" precheck (required = ... + pp_json_escaped_len(val) + ...; if (*pos + required + ... > bufsize) return;) specifically intended so a field is only ever emitted whole-or-not-at-all, which — if working as documented — should mean the truncation branch inside the loop shouldn't fire for a value that already passed the precheck. So this loop may not actually be the site that produced this specific corruption; it's more likely the raw comment/docstring text gets copied into a size-capped buffer earlier, during comment extraction, with the same byte-oriented (not UTF-8-boundary-aware) cut. I didn't have that extraction step in the source I was working from to pin the exact line — filing this with the strongest concrete evidence I have (the corrupted output + the byte-oriented pattern found) rather than guessing further.

Suggested fix direction

Wherever a string gets truncated to a byte budget (docstring capture, and/or the append_json_string JSON-escape loop as defense in depth), snap the cut point back to the last complete UTF-8 character boundary instead of cutting mid-sequence by raw byte count — or drop the trailing partial multi-byte sequence rather than emitting it.


Happy to share the indexed ec-cube.db (or a minimal repro file) if useful — found this while building tooling on top of the exported SQLite graph.

Activity

  1. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Jul 14, 2026
  2. added this to the 0.9.1-rc milestone on Jul 14, 2026
  3. DeusData commented on Jul 14, 2026

    @DeusData
    Owner

    Thanks for the byte-level evidence and public EC-CUBE reproduction. This is a high-priority 0.9.1-rc correctness bug because one partial UTF-8 sequence can make an entire SQLite result unreadable to strict clients. The fix needs a red regression at the actual truncation boundary and the same UTF-8-safe behavior in sequential and parallel serialization paths.

  4. harshitaajoshi commented on Jul 15, 2026

    @harshitaajoshi
    Contributor

    I'd like to work on this one. I reproduced the issue around byte-based truncation in extract_comment_text() and the related append_json_string() paths. I’ll add a regression test with multi-byte UTF-8 at the truncation boundary and keep the fix focused on snapping truncation back to a valid codepoint boundary.

  5. added a commit that references this issue on Jul 16, 2026
  6. DeusData commented on Jul 16, 2026

    @DeusData
    Owner

    Fixed on main in e2d41d9 (PR #1094): comment/docstring truncation now snaps the cut point back to a complete UTF-8 codepoint boundary, so a multi-byte character straddling the 500-byte limit is no longer split into an invalid trailing sequence. Thanks for the report!

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

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.windowsWindows-specific issues

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions