From b0788eea80d91816d20c1cbf9d15fa1372f17a3b Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Tue, 8 Sep 2026 16:57:02 +0700 Subject: [PATCH] fix(codex): backslash parity in the TOML closing-quote scan Round 2's escape check rejected a quote whenever a single backslash preceded it, so an escaped backslash before a real closing quote (e.g. path = "path\\" # c) was misread as an escaped quote and the field was dropped. Now counts the run of backslashes and only treats an ODD run as escaping the quote. Folded the old two-branch quote handling into one scan (it already finds the terminator correctly with or without a trailing comment) and decode \\ -> \ in the extracted value, since the scan already knows which backslash run is real. --- server/__tests__/codex-discovery.test.js | 10 ++++++++++ server/lib/codex-discovery.js | 25 ++++++++++++------------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/server/__tests__/codex-discovery.test.js b/server/__tests__/codex-discovery.test.js index b7cf0a0b..6e325bff 100644 --- a/server/__tests__/codex-discovery.test.js +++ b/server/__tests__/codex-discovery.test.js @@ -98,6 +98,16 @@ describe("codex-discovery", () => { assert.equal(meta.description, 'say \\"hi\\"'); }); + it("recognises a quote preceded by an escaped backslash as a real terminator", () => { + const meta = cx.readTomlMeta('path = "path\\\\" # c\n'); + assert.equal(meta.path, "path\\"); + }); + + it("keeps counting backslash pairs correctly with four of them before the terminator", () => { + const meta = cx.readTomlMeta('key = "a\\\\\\\\" # c\n'); + assert.equal(meta.key, "a\\\\"); + }); + it("reads a rule file's preview", () => { write( path.join(TMP, "rules", "no-force-push.rules"), diff --git a/server/lib/codex-discovery.js b/server/lib/codex-discovery.js index 82aed0a8..d026dca0 100644 --- a/server/lib/codex-discovery.js +++ b/server/lib/codex-discovery.js @@ -119,25 +119,26 @@ function readTomlMeta(text) { if (!value.slice(3).includes('"""')) inTripleQuote = true; continue; } - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } else if (value.startsWith('"') || value.startsWith("'")) { - // A quoted value with a trailing `# comment` outside the string — - // e.g. model = "gpt-5.4" # default. Find the real closing quote - // (skipping a `\"` escaped one) and strip everything after it. + if (value.startsWith('"') || value.startsWith("'")) { + // Find the real closing quote — one not preceded by an ODD run of + // backslashes (an even run, including zero, means it isn't escaped) — + // so a trailing `# comment` outside the string is stripped without + // being fooled by an escaped quote or an escaped backslash inside it. const quote = value[0]; let closeIdx = -1; for (let i = 1; i < value.length; i++) { - if (value[i] === quote && value[i - 1] !== "\\") { + if (value[i] !== quote) continue; + let backslashes = 0; + while (value[i - 1 - backslashes] === "\\") backslashes++; + if (backslashes % 2 === 0) { closeIdx = i; break; } } - if (closeIdx > 0) value = value.slice(1, closeIdx); - else continue; + if (closeIdx < 0) continue; + // \\ decodes to one backslash; other escapes (e.g. \") are left + // literal — this is a best-effort reader, not a full TOML unescaper. + value = value.slice(1, closeIdx).replace(/\\\\/g, "\\"); } else if (/^(true|false|-?\d+(\.\d+)?)\s*(#.*)?$/.test(value)) { value = value.replace(/\s*#.*$/, ""); value = value === "true" ? true : value === "false" ? false : Number(value);