Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ko/reference/cli/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ probe이며, `--wait`는 준비 또는 timeout까지 polling하지만 종단 `fa
해당할 때 서비스 마이그레이션을 설명합니다. 이 진단에 표시되는 경로는 OS 사용자 이름을 마스킹합니다.
doctor는 복구 힌트를 보여 주지만 직접 적용하지는 않습니다.

프로젝트 설정 진단은 `developer_instructions` 같은 TOML 여러 줄 문자열 안의 공급자 예시를
무시합니다. 종료 구분자 바로 앞에 이스케이프된 따옴표가 있어도, 문자열이 끝난 뒤의 실제
공급자 및 프로필 설정은 계속 검사합니다.

**OAuth 안정성** 섹션은 자격 증명 저장소에 쓰기 가능한지, `OPENCODEX_HOME` 아래에 refresh
single-flight/lock 파일을 만들 수 있는지, 건강하지 않은 OAuth 또는 Codex pool 계정(마스킹된 ID)과
복구용 `Action:`, 그리고 Codex 전달 경로가 공식 클라이언트 메타데이터를 꾸며 내지 않는다는
Expand Down
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/reference/cli/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ and pending history migration. The Codex app-home targeting section also detects
Orca runtime-home mismatch and explains service migration when applicable. Paths shown by this
diagnostic redact the OS username. Doctor prints repair hints but does not apply them.

Project-config diagnostics ignore provider examples inside TOML multiline strings, including
`developer_instructions`. Real provider and profile settings after the closing delimiter are still
checked, even when an escaped quote immediately precedes that delimiter.

The **OAuth reliability** section reports whether credential storage is writable, whether refresh
single-flight/lock files can be created under `OPENCODEX_HOME`, non-healthy OAuth or Codex pool
accounts (redacted ids) with a recovery `Action:`, and a static OK that the Codex forward path does
Expand Down
4 changes: 3 additions & 1 deletion src/codex/project-config-warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ function multilineCloseIndex(
backslashes += 1;
}
if (backslashes % 2 === 0) break;
index = line.indexOf(delimiter, index + delimiter.length);
// An escaped quote can overlap the real terminator (backslash plus four quotes).
// Keep overlapping candidates instead of skipping the entire rejected delimiter.
index = line.indexOf(delimiter, index + 1);
}
return index;
}
Expand Down
43 changes: 43 additions & 0 deletions tests/codex-integration/project-config-warnings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,49 @@ describe("parseTomlDocument", () => {
const valid = parseTomlDocument('model_provider = "provider\\\\name"');
expect(valid.root.model_provider).toBe("provider\\name");
}, 2_000);

for (const scenario of [
{ name: "root override", sameLine: false, tail: ['model_provider = "custom"'],
code: "model_provider_root", via: "root", profileName: null },
{ name: "same-line string", sameLine: true, tail: ['model_provider = "custom"'],
code: "model_provider_root", via: "root", profileName: null },
{ name: "selected profile", sameLine: false,
tail: ['profile = "work"', '[profiles.work]', 'model_provider = "custom"'],
code: "profile_selector", via: "profile", profileName: "work" },
{ name: "selected provider table", sameLine: false,
tail: ['model_provider = "custom"', '[model_providers.custom]', 'name = "Custom"'],
code: "model_providers_table", via: "root", profileName: null },
] as const) {
test(`overlapping multiline terminator preserves ${scenario.name} diagnostics`, () => {
const text = ['developer_instructions = """' + (scenario.sameLine ? "" : "\n")
+ "foo" + "\\" + '"'.repeat(4), ...scenario.tail].join("\n");
// Independent TOML parsing proves the escaped quote is followed by a real terminator.
expect(Bun.TOML.parse(text).developer_instructions).toBe('foo"');
expect(resolveEffectiveProjectModelProvider(text)).toEqual({
provider: "custom", profileName: scenario.profileName, via: scenario.via,
});
const warnings = analyzeProjectCodexConfig(text, "fixture/.codex/config.toml");
expect(warnings).toHaveLength(1);
expect(warnings[0]).toMatchObject({ code: scenario.code, detail: "custom" });
expect(warnings[0]!.profileName).toBe(scenario.profileName ?? undefined);
if (scenario.code === "model_providers_table") {
expect(parseTomlDocument(text).sections.get("model_providers.custom")?.name).toBe("Custom");
}
});
}

test("escaped three quotes keep fake routing inside the multiline body", () => {
const text = ['developer_instructions = """', "foo" + "\\" + '"'.repeat(3),
'model_provider = "custom"', '[model_providers.custom]', 'name = "Custom"',
'"""', 'model_provider = "openai"'].join("\n");
const parsedByBun = Bun.TOML.parse(text);
expect(parsedByBun.model_provider).toBe("openai");
expect(parsedByBun.developer_instructions).toContain('[model_providers.custom]');
const parsed = parseTomlDocument(text);
expect(parsed.root.model_provider).toBe("openai");
expect(parsed.sections.has("model_providers.custom")).toBe(false);
expect(analyzeProjectCodexConfig(text, "fixture/.codex/config.toml")).toEqual([]);
});
});

describe("parseTrustedProjectPathsFromCodexConfig", () => {
Expand Down
Loading