Skip to content

Commit beccd6f

Browse files
lidge-junWibias
andcommitted
feat(diagnostics): warn when project .codex/config.toml bypasses OpenCodex (cherry-pick PR #70)
Cherry-pick of 98d227d + b19384c from feat/project-codex-config-warnings. All 5 review feedback items addressed by contributor. Additional fix: - Test isolation: set CODEX_HOME in project-config-warnings.test.ts to prevent reading real user config during tests - Fix cache test to use collectProjectCodexConfigWarnings with explicit cwd Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
1 parent 79deb8e commit beccd6f

10 files changed

Lines changed: 686 additions & 1 deletion

File tree

gui/src/i18n/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export const en = {
5959
"dash.syncOk": "Sync complete. {count} model(s) appended.",
6060
"dash.syncStaleHint": "If Codex App still shows an older list, restart its long-lived app-server process.",
6161
"dash.syncFailed": "Sync failed: {error}",
62+
"dash.projectConfigTitle": "Project Codex config bypasses OpenCodex",
63+
"dash.projectConfigHint": "These repo-local settings override the OpenCodex proxy (e.g. route to OpenCode Go directly). Remove them so ~/.codex/config.toml routing applies in that project.",
6264
"dash.checkUpdate": "Check update",
6365
"dash.updateTitle": "Update opencodex",
6466
"dash.updateDesc": "Check npm for the selected channel, then choose whether to restart the proxy after installation.",

gui/src/i18n/ko.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export const ko: Record<TKey, string> = {
5959
"dash.syncOk": "동기화 완료. {count}개 모델이 추가됐습니다.",
6060
"dash.syncStaleHint": "Codex App에 여전히 예전 목록이 보이면 오래 실행 중인 app-server 프로세스를 재시작하세요.",
6161
"dash.syncFailed": "동기화 실패: {error}",
62+
"dash.projectConfigTitle": "프로젝트 Codex 설정이 OpenCodex를 우회합니다",
63+
"dash.projectConfigHint": "저장소 로컬 설정이 OpenCodex 프록시를 덮어씁니다(예: OpenCode Go로 직접 라우팅). 해당 프로젝트에서 ~/.codex/config.toml 프록시를 쓰려면 제거하세요.",
6264
"dash.checkUpdate": "업데이트 확인",
6365
"dash.updateTitle": "opencodex 업데이트",
6466
"dash.updateDesc": "선택한 채널의 npm 최신 버전을 확인한 뒤, 설치 후 프록시를 재시작할지 선택합니다.",

gui/src/i18n/zh.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ export const zh: Record<TKey, string> = {
5959
"dash.syncOk": "同步完成。已追加 {count} 个模型。",
6060
"dash.syncStaleHint": "如果 Codex App 仍显示旧列表,请重启长期运行的 app-server 进程。",
6161
"dash.syncFailed": "同步失败:{error}",
62+
"dash.projectConfigTitle": "项目 Codex 配置绕过了 OpenCodex",
63+
"dash.projectConfigHint": "这些仓库级设置会覆盖 OpenCodex 代理(例如直接走 OpenCode Go)。请移除它们,以便该项目使用 ~/.codex/config.toml 的代理路由。",
6264
"dash.checkUpdate": "检查更新",
6365
"dash.updateTitle": "更新 opencodex",
6466
"dash.updateDesc": "检查所选 npm 频道的最新版本,然后选择安装后是否重启代理。",

gui/src/pages/Dashboard.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ interface SyncResult {
2323
message: string;
2424
warning?: string;
2525
staleAppServerHint?: string;
26+
projectConfigWarnings?: ProjectCodexConfigWarning[];
27+
}
28+
interface ProjectCodexConfigWarning {
29+
path: string;
30+
code: string;
31+
detail: string;
32+
message: string;
33+
}
34+
interface ProjectCodexConfigGroup {
35+
path: string;
36+
issues: string[];
37+
bypass: string;
2638
}
2739
interface UpdateCheckData {
2840
currentVersion: string;
@@ -76,6 +88,7 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
7688
const [maHelpOpen, setMaHelpOpen] = useState(false);
7789
const [syncResult, setSyncResult] = useState<SyncResult | null>(null);
7890
const [syncError, setSyncError] = useState<string | null>(null);
91+
const [projectConfigWarnings, setProjectConfigWarnings] = useState<ProjectCodexConfigGroup[]>([]);
7992
const [updateOpen, setUpdateOpen] = useState(false);
8093
const [updateChannel, setUpdateChannel] = useState<UpdateChannel>("latest");
8194
const [updateRestart, setUpdateRestart] = useState(true);
@@ -120,6 +133,21 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
120133
return () => clearInterval(interval);
121134
}, [apiBase]);
122135

136+
useEffect(() => {
137+
const fetchDiagnostics = async () => {
138+
try {
139+
const pcRes = await fetch(`${apiBase}/api/diagnostics/project-config`);
140+
const pcData = pcRes.ok ? await pcRes.json() as { grouped?: ProjectCodexConfigGroup[] } : null;
141+
setProjectConfigWarnings(pcData?.grouped ?? []);
142+
} catch {
143+
setProjectConfigWarnings([]);
144+
}
145+
};
146+
void fetchDiagnostics();
147+
const interval = setInterval(() => void fetchDiagnostics(), 30_000);
148+
return () => clearInterval(interval);
149+
}, [apiBase]);
150+
123151
useEffect(() => {
124152
if (error) return;
125153
setModelsLoading(true);
@@ -256,6 +284,8 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
256284
const data = await res.json() as SyncResult | { error?: string };
257285
if (!res.ok) throw new Error("error" in data && data.error ? data.error : "sync failed");
258286
setSyncResult(data as SyncResult);
287+
const grouped = (data as SyncResult & { projectConfigGrouped?: ProjectCodexConfigGroup[] }).projectConfigGrouped;
288+
if (grouped) setProjectConfigWarnings(grouped);
259289
} catch (err) {
260290
setSyncError(err instanceof Error ? err.message : String(err));
261291
} finally {
@@ -374,6 +404,24 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
374404
</div>
375405
</div>
376406

407+
{projectConfigWarnings.length > 0 && (
408+
<div className="notice notice-err maintenance-notice" style={{ marginBottom: 24 }} role="alert">
409+
<IconAlert />
410+
<div>
411+
<div style={{ fontWeight: 650 }}>{t("dash.projectConfigTitle")}</div>
412+
<div className="muted" style={{ fontSize: 13, marginTop: 4 }}>{t("dash.projectConfigHint")}</div>
413+
<ul style={{ margin: "10px 0 0", paddingLeft: 18, fontSize: 13 }}>
414+
{projectConfigWarnings.map(g => (
415+
<li key={g.path} style={{ marginBottom: 8 }}>
416+
<code>{g.path}</code>{g.issues.join(", ")}
417+
<div className="muted" style={{ marginTop: 2 }}>{g.bypass}</div>
418+
</li>
419+
))}
420+
</ul>
421+
</div>
422+
</div>
423+
)}
424+
377425
<div className="panel maintenance-panel" style={{ marginBottom: 24 }}>
378426
<div className="spread maintenance-head">
379427
<div>

src/cli/doctor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { readCodexTokens } from "../codex/auth-collision";
1515
import { resolveCodexHomeDir as resolveCodexHomeDirImpl, isWslRuntime, listWslWindowsCodexHomes, wslAutomountRoot, type CodexHomeDeps } from "../codex/home";
1616
import { findCodexOnPath, isWindowsInteropDir } from "../codex/shim";
1717
import { countPendingOpencodexHistory } from "../codex/history-provider";
18+
import { collectProjectCodexConfigWarnings, formatProjectCodexConfigWarningsForDoctor } from "../codex/project-config-warnings";
1819
export { resolveCodexHomeDir } from "../codex/home";
1920

2021
const WHAM_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
@@ -373,6 +374,16 @@ export async function runDoctor(): Promise<void> {
373374
console.log(` -- ${pending.pendingRows} thread(s) still tagged opencodex, ${pending.backupEntries} backup manifest entr${pending.backupEntries === 1 ? "y" : "ies"}`);
374375
}
375376

377+
console.log("\nProject Codex configs");
378+
const projectWarnings = collectProjectCodexConfigWarnings();
379+
if (projectWarnings.length === 0) {
380+
console.log(" ok no project-local provider bypass detected");
381+
} else {
382+
for (const line of formatProjectCodexConfigWarningsForDoctor(projectWarnings)) {
383+
console.log(line);
384+
}
385+
}
386+
376387
const dual = collectWslDualInstall();
377388
if (dual.wsl) {
378389
console.log("\nWSL Codex installs");

0 commit comments

Comments
 (0)