From 4e85ab4bc4de10274bfa939cb60f78d8e344fa94 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Mon, 10 Aug 2026 11:59:49 +0900 Subject: [PATCH] fix(grok): preserve aliases for nested model tables --- src/grok/inject.ts | 15 ++++----------- tests/grok-config-inject.test.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/grok/inject.ts b/src/grok/inject.ts index 9304d923ca..cfcac6b0e2 100644 --- a/src/grok/inject.ts +++ b/src/grok/inject.ts @@ -72,19 +72,12 @@ export function findManagedRegion(content: string): ManagedRegion | null { */ const KEY_SEGMENT = String.raw`(?:[A-Za-z0-9_-]+|"(?:[^"\\]|\\.)*"|'[^']*')`; /** - * User-owned model table headers. Also matches array-of-table (`[[model.x]]`) and sub-table - * (`[model.x.sub]`) spellings. `[[model.x]]` genuinely collides with a generated `[model.x]`, - * and one collision makes grok reject the ENTIRE config layer ("duplicate key"), taking every - * unrelated user setting with it; `[model.x.sub]` does not strictly collide, but reserving it - * costs only a suffixed alias and keeps us clear of the user's namespace. - * - * Every character class here is newline-free ON PURPOSE. With `[^\]]*` the optional sub-table - * tail runs past the end of its own line, so an unclosed `[model.…` inside a multiline string - * swallows the following lines — including a real `[model.]` header, which then goes - * unreserved and produces the very duplicate-key config this scan exists to prevent. + * User-owned model table headers. Also matches the array-of-table (`[[model.x]]`) spelling, + * which genuinely collides with a generated `[model.x]`. A sub-table (`[model.x.extra]`) does + * not collide: TOML permits its implicitly created parent to be defined explicitly later. */ const MODEL_TABLE_HEADER = new RegExp( - String.raw`^[ \t]*\[\[?[ \t]*(${KEY_SEGMENT})[ \t]*\.[ \t]*(${KEY_SEGMENT})[ \t]*(?:\.[^\]\r\n]*)?\]\]?[ \t]*(?:#.*)?$`, + String.raw`^[ \t]*\[\[?[ \t]*(${KEY_SEGMENT})[ \t]*\.[ \t]*(${KEY_SEGMENT})[ \t]*\]\]?[ \t]*(?:#.*)?$`, "gm", ); diff --git a/tests/grok-config-inject.test.ts b/tests/grok-config-inject.test.ts index 6e89f44c1d..bdfa114f21 100644 --- a/tests/grok-config-inject.test.ts +++ b/tests/grok-config-inject.test.ts @@ -188,7 +188,6 @@ describe("Grok config injection", () => { ["mixed quoting with whitespace", `[ "model" . 'ocx-mine' ]`], ["bare (baseline)", "[model.ocx-mine]"], ["array of tables", "[[model.ocx-mine]]"], - ["sub-table", "[model.ocx-mine.extra]"], ["trailing comment", '[model."ocx-mine"] # mine'], ]; @@ -205,6 +204,18 @@ describe("Grok config injection", () => { }); } + test("does not reserve an alias from a user sub-table", () => { + writeFileSync(configPath(), "[model.ocx-mine.extra]\nx = 1\n", "utf8"); + + injectGrokConfig(10100, [{ id: "mine" }], { grokHome }); + + const written = readFileSync(configPath(), "utf8"); + const generated = written.slice(written.indexOf(BEGIN_MARKER)); + expect(written).toContain("[model.ocx-mine.extra]"); + expect(generated).toContain("[model.ocx-mine]\n"); + expect(generated).not.toContain("[model.ocx-mine-2]"); + }); + test("does not reserve aliases from unrelated tables", () => { // [models.*] and [model_providers.*] are different tables entirely — reserving from them // would needlessly suffix our aliases.