From 2fee196227a67dd479a6fcd0bc617ce9400a471e Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sat, 17 Oct 2026 20:47:05 -0400 Subject: [PATCH] fix: read a Codex config whose multi-line string holds non-ASCII text `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 Signed-off-by: Rudy Celekli --- crates/graphicle/src/codex_config.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/graphicle/src/codex_config.rs b/crates/graphicle/src/codex_config.rs index a8a8185..17d0e36 100644 --- a/crates/graphicle/src/codex_config.rs +++ b/crates/graphicle/src/codex_config.rs @@ -88,7 +88,9 @@ fn scan(line: &str, open: &mut Option<&'static str>, depth: &mut usize) { let mut i = 0; while i < bytes.len() { if let Some(delimiter) = *open { - if line[i..].starts_with(delimiter) { + // Bytes, not `line[i..]`: inside a string `i` steps one byte at + // a time and can stop inside a multi-byte character. + if bytes[i..].starts_with(delimiter.as_bytes()) { *open = None; i += 3; } else { @@ -545,6 +547,16 @@ model = "o4-mini" assert!(remove(CONFIG, "graphicle-app").is_none()); } + #[test] + fn tables_read_past_multi_line_strings_with_non_ascii_text() { + let text = "developer_instructions = \"\"\"\nAnswer in fran\u{e7}ais \u{2014} briefly.\n\"\"\"\nnotes = '''caf\u{e9}'''\n\n[mcp_servers.docs]\ncommand = \"npx\"\nargs = [\"docs-mcp\"]\n"; + let found = tables(text); + assert_eq!(found.len(), 1); + assert_eq!(found[0].name, "docs"); + assert_eq!(found[0].command.as_deref(), Some("npx")); + assert!(!inline_servers(text)); + } + #[test] fn removing_a_table_keeps_the_comments_of_the_next() { let text = "[mcp_servers.graphify]\ncommand = \"graphify\"\nargs = [\"--mcp\"]\n\n# Mine.\n[mcp_servers.mine]\ncommand = \"m\"\n";