@@ -87,24 +87,38 @@ function withAuth(client: ZulipClient, init?: RequestInit): RequestInit {
8787export async function parseJsonOrThrow ( res : Response ) : Promise < unknown > {
8888 const text = await res . text ( ) ;
8989
90- // Zulip API endpoints should return JSON. If we get HTML (common with SSO/Cloudflare Access),
91- // treat it as an auth error so we don't report false positives like "Sent".
90+ // Zulip API endpoints should return JSON.
91+ // If we get HTML, it's often an auth/SSO/proxy login page (Cloudflare Access, SSO, etc.),
92+ // but "non-JSON" can also be an upstream error page (502/503) or a misconfigured base URL.
9293 const contentType = ( res . headers . get ( "content-type" ) || "" ) . toLowerCase ( ) ;
93- const looksLikeHtml = / ^ \s * < ! d o c t y p e h t m l / i. test ( text ) || / ^ \s * < h t m l / i. test ( text ) ;
94- const expectsJson =
95- contentType . includes ( "application/json" ) || contentType . includes ( "application/" ) ;
94+ const looksLikeHtml =
95+ / ^ \s * < ! d o c t y p e h t m l / i. test ( text ) ||
96+ / ^ \s * < h t m l / i. test ( text ) ||
97+ / ^ \s * < h e a d / i. test ( text ) ||
98+ / ^ \s * < m e t a \b / i. test ( text ) ;
9699
97100 let payload : Record < string , unknown > ;
98101 try {
99102 payload = text ? JSON . parse ( text ) : { } ;
100103 } catch {
101- if ( looksLikeHtml || ! expectsJson ) {
102- const hint =
103- "Non-JSON response from Zulip. This usually means a reverse-proxy/SSO (e.g. Cloudflare Access) is blocking /api. " +
104- "Allow bot access to /api/v1/* (service token / bypass policy) or expose an internal API URL." ;
105- throw new Error ( `Zulip API error: ${ hint } ` ) ;
104+ const snippet = text . trim ( ) . slice ( 0 , 240 ) . replace ( / \s + / g, " " ) ;
105+
106+ if ( looksLikeHtml ) {
107+ throw new Error (
108+ "Zulip API error: received HTML instead of JSON from /api. " +
109+ `HTTP ${ res . status } (content-type: ${ contentType || "unknown" } ). ` +
110+ "This typically means an auth/SSO/proxy layer is intercepting API requests. " +
111+ "Allow bot access to /api/v1/* (service token / bypass policy) or use an internal API base URL. " +
112+ ( snippet ? `Snippet: ${ snippet } ` : "" ) ,
113+ ) ;
106114 }
107- throw new Error ( `Zulip API error: invalid JSON response (HTTP ${ res . status } )` ) ;
115+
116+ throw new Error (
117+ "Zulip API error: received non-JSON response from /api. " +
118+ `HTTP ${ res . status } (content-type: ${ contentType || "unknown" } ). ` +
119+ "This can be caused by a proxy/load balancer error (502/503), a misconfigured base URL, or an auth layer. " +
120+ ( snippet ? `Snippet: ${ snippet } ` : "" ) ,
121+ ) ;
108122 }
109123
110124 const msgField =
0 commit comments