Skip to content

fix: read a Codex config whose multi-line string holds non-ASCII text - #3

Merged
VikramLex merged 1 commit into
Graphicle-AI:mainfrom
rudycelekli:fix/codex-config-multibyte-multiline-string
Oct 18, 2026
Merged

VikramLex merged 1 commit into
Graphicle-AI:mainfrom
rudycelekli:fix/codex-config-multibyte-multiline-string

Conversation

@rudycelekli

Copy link
Copy Markdown
Contributor

graphicle connect codex, graphicle disconnect and agent detection panic when ~/.codex/config.toml holds a multi-line string with a non-ASCII character in it.

What happens

A Codex config like this one:

developer_instructions = """
Answer in français — briefly.
"""

[mcp_servers.docs]
command = "npx"

makes every reader of the file panic:

byte index 8 is not a char boundary; it is inside 'é' (bytes 7..9) of `Answer in français — briefly.`

Root cause

codex_config::scan follows an open multi-line string (""" or ''') one byte at a time and tested for its closing delimiter with line[i..].starts_with(delimiter). Inside the string, i can stop on the second byte of a multi-byte character, and slicing the &str there panics. All three readers of the file (agent_detect, agent_setup for connect codex, disconnect) go through tables() → classify() → scan().

Change

The delimiter test compares bytes (bytes[i..].starts_with(delimiter.as_bytes())), so it never slices the line. Nothing else changes: the delimiters are ASCII, so the comparison finds the same positions as before.

Test

codex_config::tests::tables_read_past_multi_line_strings_with_non_ascii_text: a basic and a literal multi-line string holding ç, — and é, followed by an [mcp_servers.docs] table, which is found with its command. On main the test panics as shown above.

Verification

In crates/graphicle, with Rust 1.95.0:

  • cargo fmt --check: clean
  • cargo clippy --locked --all-targets -- -D warnings: clean on Rust 1.95.0 (macOS). Not run locally with --features local-inference; CI covers that.
  • cargo test --locked --all-targets: 690 passed, 0 failed.

I left CHANGELOG.md to you.

🤖 Generated with Claude Code

`codex_config::scan` follows a multi-line string one byte at a time and
tested for its closing delimiter with `line[i..].starts_with(...)`. When
the string holds a multi-byte character (an accent, an em dash), `i`
stops inside it and the slice panics: "byte index is not a char
boundary". Every reader of `~/.codex/config.toml` goes through `scan`
(agent detection, `graphicle connect codex`, `graphicle disconnect`), so
one `developer_instructions = """..."""` with an accented word made them
panic instead of finding the `[mcp_servers.*]` tables.

The delimiter test now compares bytes, which never slices the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
@VikramLex
VikramLex merged commit 819a0fd into Graphicle-AI:main Oct 18, 2026
VikramLex added a commit that referenced this pull request Oct 18, 2026
…-off

Dashboard: the app that `graphicle open` shows, and its product and
documentation pages, get a light theme. It follows the operating
system's light or dark setting; Settings > Appearance picks System,
Light or Dark, kept per browser. The theme is set before the first
paint by /theme.js (the Content-Security-Policy allows no inline
script). Every colour of the stylesheets is a token whose dark value is
the old colour, so the dark theme is unchanged; in the light theme, text
is at least 4.5:1 against its background, and control borders and focus
rings at least 3:1.

MCP Registry: from this release on, the mcp-registry workflow adds one
MCP bundle (.mcpb) for each release target to the GitHub release and
lists the release as io.github.Graphicle-AI/graphicle
(packaging/mcpb/, .github/workflows/mcp-registry.yml, glama.json).

Update check: each check is recorded before its request goes out, so a
check still running when a command ends counts as that day's check
instead of running again with the next command. Checks whose requests
fail, or that GitHub refuses or limits, back off a day, then two, then
four. Still one GET with no code or data, and the notice at most once a
day.

Other changes:
- The rcfile tests that use the process-wide state hold a lock, so one
  test can no longer clear the warned folders between another's calls.
- The engine's file ledger marks each application module with a passing
  comparison receipt against Graphify 0.9.91 as verified; STATUS.md,
  AUDIT.json and PROGRESS.json describe the 0.9.91 port.
- cargo-deny ignores RUSTSEC-2026-0367, RUSTSEC-2026-0368 and
  RUSTSEC-2026-0369 (zip 8.6.0): they affect only zip's extract-to-disk
  APIs, and Graphicle reads archive members in memory and never
  extracts them. The ignores go once calamine moves to zip 9.
- Every crate is at 0.1.2, with a CHANGELOG entry. It also credits the
  four contributed fixes merged just before this commit: #3 (a Codex
  config with non-ASCII text in a multi-line string), #4 (a conversation
  opening cut on a character boundary), #5 (initialize answers a protocol
  version the engine speaks) and #6 (Method not found for an unknown MCP
  method), all by @rudycelekli.

Closes #2

Signed-off-by: VikramLex <vikramlex@karlexai.com>
@VikramLex

Copy link
Copy Markdown
Member

Thank you, @rudycelekli. Merged for 0.1.2: comparing the closing delimiter on bytes is exactly right, and the test pins it down. The CHANGELOG credits you.

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.

2 participants