@@ -1228,14 +1228,15 @@ func registerAuthCommands(configDir string) {
12281228 if os .Getenv ("DCI_API_KEY" ) != "" {
12291229 return fmt .Errorf ("login is not needed when DCI_API_KEY is set" )
12301230 }
1231- // Trigger the OAuth flow by calling a lightweight endpoint.
1232- // Suppress the validate response body — login only needs the OAuth
1233- // side effect (token cached), not the API output .
1231+ // Trigger the OAuth flow via validate. This call is internal (login
1232+ // only needs the cached token), so isolate all its observable effects
1233+ // — stdout, guard stderr, guard exit-code flag — from the login result .
12341234 os .Args = []string {os .Args [0 ], "dci" , "validate" }
1235- oldOut := cli .Stdout
1236- cli .Stdout = io .Discard
1235+ oldOut , oldErr := cli .Stdout , cli . Stderr
1236+ cli .Stdout , cli . Stderr = io . Discard , io .Discard
12371237 err := cli .Run ()
1238- cli .Stdout = oldOut
1238+ cli .Stdout , cli .Stderr = oldOut , oldErr
1239+ nonJSONErrorResponse = false
12391240
12401241 // Auto-configure DoiT employees who have no customer context set.
12411242 // The validate endpoint requires customerContext for @doit.com accounts,
@@ -1443,12 +1444,12 @@ func (g dciResponseGuard) Format(resp cli.Response) error {
14431444 return nil
14441445 }
14451446 if msg , ok := jsonApplicationError (resp ); ok {
1446- // Print the structured error body as usual, then flag a non-zero exit.
14471447 nonJSONErrorResponse = true
14481448 if err := g .next .Format (resp ); err != nil {
14491449 return err
14501450 }
1451- fmt .Fprintf (os .Stderr , "Error: the DoiT API returned an application error: %s\n " , msg )
1451+ // cli.Stderr (not os.Stderr) so callers like login can suppress it; don't revert.
1452+ fmt .Fprintf (cli .Stderr , "Error: the DoiT API returned an application error: %s\n " , msg )
14521453 return nil
14531454 }
14541455 return g .next .Format (resp )
@@ -1480,17 +1481,30 @@ func isHTMLErrorPage(resp cli.Response) bool {
14801481}
14811482
14821483func bodyLooksLikeHTML (s string ) bool {
1483- s = strings .ToLower (strings .TrimSpace (s ))
1484+ s = strings .TrimSpace (strings .TrimPrefix (s , "\ufeff " )) // strip UTF-8 BOM some proxies prepend
1485+ if len (s ) > 16 {
1486+ s = s [:16 ] // only the prefix matters; avoid lowercasing the whole body
1487+ }
1488+ s = strings .ToLower (s )
14841489 return strings .HasPrefix (s , "<!doctype html" ) ||
14851490 strings .HasPrefix (s , "<html" ) ||
14861491 strings .HasPrefix (s , "<head" ) ||
1487- strings .HasPrefix (s , "<body" )
1492+ strings .HasPrefix (s , "<body" ) ||
1493+ strings .HasPrefix (s , "<?xml" ) ||
1494+ strings .HasPrefix (s , "<!--" )
14881495}
14891496
14901497// jsonApplicationError reports whether a 2xx response carries a non-empty
14911498// top-level JSON `error` field (e.g. an AVA askSync failure under a locked 2xx
14921499// status), returning a human-readable message. Such a body is not a valid DCI
14931500// success shape, so it is treated as a failure.
1501+ //
1502+ // The object-only, top-level-only scoping is load-bearing, verified against the
1503+ // OpenAPI spec: the only other 2xx body carrying a top-level `error` is
1504+ // POST /insights/v1/results, whose 200 is a top-level *array* of per-row error
1505+ // items. The map assertion below lets that array pass through untouched. Do not
1506+ // recurse into arrays or nested objects here, or it will false-positive on
1507+ // legitimate partial-result responses.
14941508func jsonApplicationError (resp cli.Response ) (string , bool ) {
14951509 if resp .Status < 200 || resp .Status >= 300 {
14961510 return "" , false
@@ -1506,6 +1520,7 @@ func jsonApplicationError(resp cli.Response) (string, bool) {
15061520 }
15071521 return v , true
15081522 case map [string ]interface {}:
1523+ // Defensive only: no current DCI endpoint returns an object-typed error (AVA returns a string).
15091524 if len (v ) == 0 {
15101525 return "" , false
15111526 }
@@ -1528,7 +1543,8 @@ func printNonJSONError(resp cli.Response) {
15281543 fmt .Fprintf (& b , "Trace: %s\n " , trace )
15291544 }
15301545 b .WriteString ("Please retry in a moment. If it persists, contact DoiT support with the trace above.\n " )
1531- fmt .Fprint (os .Stderr , b .String ())
1546+ // cli.Stderr (not os.Stderr) so internal callers like login can redirect it.
1547+ fmt .Fprint (cli .Stderr , b .String ())
15321548}
15331549
15341550// traceID returns the first available upstream trace identifier so users can
0 commit comments