The remediation says to check the proxy, the VPN, the firewall, DNS, a custom CA, and WebSocket policy. On Codex CLI 0.163.0 the handshake under that sentence is a 401, and the message in the 401 is Missing bearer or basic authentication in header. DNS had already returned addresses. The HTTP reachability row was ok. Proxy environment variables were none. Auth mode was none.
Those two lines are not the same failure. The summary points at the path. The detail points at the header. Codex CLI 0.166.0, installed beside the older binary and not put on PATH, prints the same remediation and still records auth mode none. It redacts the 401 body. The flag added in that release, codex doctor --network-only, then drops the auth row and exits 0.
If you change proxy settings from the summary alone, you will be working on the sentence that is wrong for this report.
The summary drops the handshake
codex doctor --summary on an empty home printed the proxy sentence in Notes and again under Connectivity. It did not print the 401. The report without --summary printed both. The summary is a grouped view. It is not the diagnostic.
On 0.163.0 the handshake field was http 401 Unauthorized, and the JSON body inside it named Missing bearer or basic authentication in header with type invalid_request_error. The same row recorded auth mode as none, proxy env vars as none, supports websockets as true, and DNS as 2 IPv4, 2 IPv6, first IPv4. The connect timeout was 15000 ms. The check finished in a few hundred milliseconds, not at the timeout. The server answered. It refused the handshake because the header was absent.
0.166.0 printed the same summary sentence, the same auth mode, and the same DNS line. The handshake field was http 401 Unauthorized: [redacted], including when the command was repeated with --all. The body that names the missing bearer is gone. The remediation that names the proxy is still there. An upgrade does not correct the sentence. It removes the sentence that contradicted it.
The HTTP row on both binaries said the ChatGPT inference URL was reachable, HTTP 405, and the reachability mode was ChatGPT auth. No credentials were loaded. A 405 means the host answered with method not allowed. It does not mean a ChatGPT session authenticated. Read the status as reachability. Do not read the mode label as a login. The doctor already redacts the path after the host, so the useful part of that row is the status code and the mode label, not a URL you can replay.
The empty-home full doctor exited 1. overallStatus in --json was fail. The fail was auth.credentials: no Codex credentials were found. The remediation on that row is Run codex login or provide an API key through a supported auth env var. The websocket row was a warning in the same report. It was not the row that set the exit code. The footer on 0.166.0 was 18 ok | 1 idle | 2 notes | 1 warn | 1 fail failed. On 0.163.0 it was 18 ok | 1 idle | 3 notes | 1 warn | 1 fail failed. The extra note on 0.163.0 was 0.166.0 available (current 0.163.0). The word failed is the overall status glued to the count line. Do not parse it as a second failure. Read overallStatus, or read the [XX] row.
What the two binaries were given
The copy on PATH was Codex CLI 0.163.0. npm view @openai/codex version in the same session reported 0.166.0. A second copy was installed with npm install --prefix <prefix> --no-save @openai/codex@0.166.0 and was not put on PATH. The wrapper in that prefix printed codex-cli 0.166.0. The release tag rust-v0.166.0 is dated 29 October 2026. This comparison was run on 2 November 2026, on Linux x86_64.
CODEX_HOME pointed at a new directory. The working directory was a new Git repository with one commit and a marker file. Neither was a checkout in use. No codex login was run. No model turn was started. OPENAI_API_KEY and CODEX_API_KEY were unset for the empty-home cases. One later case set OPENAI_API_KEY to a non-credential string, and that case is labeled below.
This is not the sandbox comparison. An earlier note on this site used 0.163.0 codex sandbox to separate an env-file deny from a read-only mount, and it left 0.166.0 undescribed. The commands here are codex doctor and codex doctor --network-only. They do not show what a signed-in agent will ask approval for.
The doctor section of the CLI reference, read on 2 November 2026, says the command checks installation, configuration, authentication, runtime, Git, terminal, app-server, and thread inventory. --json emits a redacted report. --network-only limits checks to configuration, the network environment, provider HTTP connectivity, and Responses WebSocket connectivity. Pull request 57606, merged 28 October 2026 and listed in the 0.166.0 release, says the limited report skips unrelated diagnostics such as database and MCP scans, and that a configuration load failure stops the command before it probes guessed targets. It does not say the limited report is an authentication check.
The flag 0.163 rejects
codex doctor --network-only --json on 0.163.0 wrote nothing to standard output and exited 2. Standard error was:
error: unexpected argument '--network-only' found
Usage: codex doctor [OPTIONS]
For more information, try '--help'.
codex doctor --help on that binary does not list the flag. A script written against the 0.166.0 reference will get a usage error from the binary that is still on many PATHs. Exit 2 here is the argument parser. The doctor did not run. It is not the fail status from a missing credential, and it is not a websocket warning. Check codex --version before you treat a rejected flag as a network fault.
0.166.0 help describes the flag as Run config, network environment, provider HTTP, and Responses WebSocket checks. That matches the reference. It does not mention authentication. The measured report matches the help, which is the problem if you wanted the auth row.
What the 0.166 flag keeps
On 0.166.0, codex doctor --network-only --json exited 0. overallStatus was warning, not fail. The report had four checks: config.load, network.env, network.provider_reachability, and network.websocket_reachability. auth.credentials was not among them. The human summary was:
3 ok | 1 notes | 1 warn | 0 fail degraded
The notes line was the websocket remediation. There was no auth line. 1 notes is the wording the binary printed. A parser that expects note or a fail count will misread that footer. The status word at the end is degraded, not failed. Exit 0 plus degraded means the limited report had a warning and no fail. It does not mean the websocket connected, and it does not mean a credential was found. The credential check was not in the report.
The four rows are still worth reading, once you know what they are. config.load was ok, with config.toml missing and the model left at <default>. network.env was ok: no proxy environment variables, system proxy respect disabled, no managed proxy. network.provider_reachability was the HTTP 405 row. network.websocket_reachability was the 401 warning. A configuration load failure, which this home did not produce, is the case the pull request says should stop the probes. A missing file that still loads as an empty config is not that failure. The command probed.
Use the flag when you already know the login state and you want those four rows without the thread inventory and MCP scan. Do not use its exit code as a login test. The full doctor on the same home exited 1 because of the auth row the flag left out. If the question is why the handshake returned 401, the command that hides that row is the wrong one.
A set variable is not a sent header
A second home received OPENAI_API_KEY set to a string that is not a credential. No auth.json was written. On both binaries, auth.credentials became ok. The summary was auth is provided by environment. The detail named OPENAI_API_KEY. The full doctor exited 0. overallStatus moved from fail to warning.
The websocket row did not move. auth mode stayed none. On 0.163.0 the handshake body was still Missing bearer or basic authentication in header. On 0.166.0 the body was still [redacted], and the status was still http 401 Unauthorized. The inventory check and the handshake probe are not the same object. A set variable satisfies the first. This doctor did not attach it to the second.
That split is easy to miss if you export a key to clear the fail and then trust the remediation that remains. The fail goes away. The warning stays, and it still names the proxy. On 0.163.0 the body still says the header was missing. auth is provided by environment is not proof the probe sent a bearer.
This run does not say what a real codex login would put on the handshake. It did not log in. It says what an empty home does, and what a present OPENAI_API_KEY changes in the report. A signed-in doctor can still warn for a proxy, a custom CA, or a policy that blocks the upgrade to WebSocket. Those reports will not look like this one. They will not have auth mode none next to a 401 whose 0.163.0 body says the bearer is missing.
Verify which exit you have
Three exits showed up. They are not interchangeable.
Exit 2, on 0.163.0 with --network-only, is an unknown flag. The doctor did not run. Fix the command or the version before you open a firewall ticket. codex doctor --help is the check. If the flag is absent, drop it and run the full report.
Exit 1, on the empty home, is the auth fail. The websocket row is a warning beside it. The cause of the non-zero status is auth.credentials, not the proxy sentence. Run codex doctor --json and read overallStatus and the auth check before you read the remediation. The JSON on 0.163.0 still contains the missing-bearer message. The JSON on 0.166.0 redacts that message and keeps http 401 Unauthorized and auth mode none. Those two fields are enough to stop a proxy change.
Exit 0 means no check in that report was a fail. On 0.166.0 --network-only, the auth check was not in the report, so a missing login cannot fail it. On the full doctor with OPENAI_API_KEY set, the auth check passed the inventory and the handshake was still a 401, so the exit was 0 with overallStatus warning. Green exit, warning report, 401 handshake. Read the warning. Do not stop at the exit code.
--summary is the view that drops the handshake field. Use it for the grouped rows. Use the full report, or --json, when the websocket row is a warning. On 0.166.0, --all does not restore the 401 body. The tool redacts it in the human report and in JSON. Plan on that if you are writing a parser. Match http 401 and auth mode, not the sentence 0.163.0 still prints.
What to change, and what to leave
If auth mode is none and the handshake is a 401, look at auth.credentials in a full doctor report before you touch proxy, VPN, or firewall settings. A fail there is a missing login or a missing env var. A pass that only lists an env var is an inventory pass. Compare it with the handshake. If the handshake still says the bearer is missing, or if 0.166.0 shows a 401 with auth mode none and a redacted body, the probe did not send that variable.
If auth mode is not none, proxy variables are set, or the DNS line is empty, the remediation is the list to work through. This run did not produce that report. Do not use these 401s as a template for a proxy failure. The network row in this run was explicitly no proxy env vars. A later run that prints a proxy variable is a different case, and the remediation may fit it.
If you are on 0.163.0 and a script passes --network-only, the script is ahead of the binary. codex doctor without the flag still prints the auth row and, on this version, the unredacted 401 body. Keep that report until the binary is 0.166.0 or newer. After the upgrade, keep the full doctor when the question is authentication. Use --network-only when the login state is already known and you want the four connectivity rows without the thread and MCP scans. The pull request is explicit that a configuration load failure skips the probes. It is not a substitute for the auth row.
The HTTP 405 row can stay green through all of this. Green reachability plus a 401 handshake is a server that answered twice: once to an HTTP probe, once to a websocket handshake with no bearer. That is the opposite of a host you cannot reach. The reachability mode label ChatGPT auth does not change that reading when the auth check failed, or when the websocket row still says auth mode none.
A practical order, on either binary, is short. Point CODEX_HOME at a directory you created for the check, so the report is not mixed with a live login. Run codex doctor with no --summary and no --network-only. Read the websocket row’s auth mode and handshake line before the remediation. If those say none and 401, read auth.credentials in the same report, or in --json. Only then decide whether the proxy sentence applies. On 0.166.0, repeat without --network-only if the first command was the limited form and the exit was 0. An exit of 0 from the limited form has not looked for credentials.
The 0.166.0 release is still the right upgrade if you want the flag, the redacted support report, and the other fixes in that tag. It is not a fix for this warning text. Both binaries measured here print the proxy remediation for a handshake whose auth mode is none. The difference is whether the body that explains the 401 is still on the page, and whether a focused network command is willing to omit the row that would have failed.