authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-03 19:47:39-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-03 21:03:16-08:00
log9db54c9b19b136526baa97677e244b4b0071ee5b
tree6a3e97c6303020c54ad122bdaa977fe08b3e09eb
parent34430e3514041935898aba5282392fac605afa71

Handle Codex auth JSON arrays and flag missing auth markers

- Parse nested/array JSON auth status output from `codex auth status --json` - Return `unauthenticated` when JSON contains `authenticated: false` - Return warning when JSON is parseable but lacks an auth marker

2 files changed, 85 insertions(+), 11 deletions(-)

apps/server/src/provider/Layers/ProviderHealth.test.ts+50
...@@ -79,6 +79,56 @@ describe("checkProviderStatusesOnStartup", () => {...@@ -79,6 +79,56 @@ describe("checkProviderStatusesOnStartup", () => {
79 ]);79 ]);
80 });80 });
8181
82 it("returns unauthenticated when auth probe JSON array includes authenticated=false", async () => {
83 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
84 if (args.join(" ") === "--version") {
85 return result({ stdout: "codex 1.0.0\n" });
86 }
87 if (args.join(" ") === "auth status --json") {
88 return result({
89 stdout: '[{"authenticated":false}]\n',
90 });
91 }
92 throw new Error(`Unexpected command: ${args.join(" ")}`);
93 });
94
95 expect(status).toEqual([
96 expect.objectContaining({
97 provider: "codex",
98 status: "error",
99 available: true,
100 authStatus: "unauthenticated",
101 message: "Codex CLI is not authenticated. Run `codex login` and try again.",
102 }),
103 ]);
104 });
105
106 it("returns warning when auth probe JSON array has no auth marker", async () => {
107 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
108 if (args.join(" ") === "--version") {
109 return result({ stdout: "codex 1.0.0\n" });
110 }
111 if (args.join(" ") === "auth status --json") {
112 return result({
113 code: 0,
114 stdout: '[{"ok":true}]\n',
115 });
116 }
117 throw new Error(`Unexpected command: ${args.join(" ")}`);
118 });
119
120 expect(status).toEqual([
121 expect.objectContaining({
122 provider: "codex",
123 status: "warning",
124 available: true,
125 authStatus: "unknown",
126 message:
127 "Could not verify Codex authentication status from JSON output (missing auth marker).",
128 }),
129 ]);
130 });
131
82 it("returns warning when auth status command is unsupported", async () => {132 it("returns warning when auth status command is unsupported", async () => {
83 const status = await checkProviderStatusesOnStartup(async (_command, args) => {133 const status = await checkProviderStatusesOnStartup(async (_command, args) => {
84 if (args.join(" ") === "--version") {134 if (args.join(" ") === "--version") {
apps/server/src/provider/Layers/ProviderHealth.ts+35-11
...@@ -54,7 +54,17 @@ function detailFromResult(result: ProcessRunResult): string | undefined {...@@ -54,7 +54,17 @@ function detailFromResult(result: ProcessRunResult): string | undefined {
54}54}
5555
56function extractAuthBoolean(value: unknown): boolean | undefined {56function extractAuthBoolean(value: unknown): boolean | undefined {
57 if (!value || typeof value !== "object" || Array.isArray(value)) {57 if (Array.isArray(value)) {
58 for (const entry of value) {
59 const nested = extractAuthBoolean(entry);
60 if (nested !== undefined) {
61 return nested;
62 }
63 }
64 return undefined;
65 }
66
67 if (!value || typeof value !== "object") {
58 return undefined;68 return undefined;
59 }69 }
6070
...@@ -122,19 +132,28 @@ function parseAuthStatusFromOutput(...@@ -122,19 +132,28 @@ function parseAuthStatusFromOutput(
122 (() => {132 (() => {
123 const trimmed = result.stdout.trim();133 const trimmed = result.stdout.trim();
124 if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {134 if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {
125 return undefined;135 return {
136 attemptedJsonParse: false as const,
137 auth: undefined as boolean | undefined,
138 };
126 }139 }
127 try {140 try {
128 return extractAuthBoolean(JSON.parse(trimmed));141 return {
142 attemptedJsonParse: true as const,
143 auth: extractAuthBoolean(JSON.parse(trimmed)),
144 };
129 } catch {145 } catch {
130 return undefined;146 return {
147 attemptedJsonParse: false as const,
148 auth: undefined as boolean | undefined,
149 };
131 }150 }
132 })() ?? undefined;151 })();
133152
134 if (parsedAuth === true) {153 if (parsedAuth.auth === true) {
135 return { status: "ready", authStatus: "authenticated" };154 return { status: "ready", authStatus: "authenticated" };
136 }155 }
137 if (parsedAuth === false) {156 if (parsedAuth.auth === false) {
138 return {157 return {
139 status: "error",158 status: "error",
140 authStatus: "unauthenticated",159 authStatus: "unauthenticated",
...@@ -142,6 +161,15 @@ function parseAuthStatusFromOutput(...@@ -142,6 +161,15 @@ function parseAuthStatusFromOutput(
142 };161 };
143 }162 }
144163
164 if (parsedAuth.attemptedJsonParse) {
165 return {
166 status: "warning",
167 authStatus: "unknown",
168 message:
169 "Could not verify Codex authentication status from JSON output (missing auth marker).",
170 };
171 }
172
145 if (result.code === 0) {173 if (result.code === 0) {
146 // Successful command with no explicit auth marker is still a pass.174 // Successful command with no explicit auth marker is still a pass.
147 return { status: "ready", authStatus: "authenticated" };175 return { status: "ready", authStatus: "authenticated" };
...@@ -240,7 +268,3 @@ const makeProviderHealth = (run: CommandRunner = runProcess) =>...@@ -240,7 +268,3 @@ const makeProviderHealth = (run: CommandRunner = runProcess) =>
240 });268 });
241269
242export const ProviderHealthLive = Layer.effect(ProviderHealth, makeProviderHealth());270export const ProviderHealthLive = Layer.effect(ProviderHealth, makeProviderHealth());
243
244export function makeProviderHealthLive(run?: CommandRunner) {
245 return Layer.effect(ProviderHealth, makeProviderHealth(run));
246}