On start, without asking, the client copies the OAuth tokens in ~/.codex/auth.json into ~/.henosis_cli_codex_auth.json and uses them with chatgpt.com and auth.openai.com; the user-typed /codex transcribe command puts the ChatGPT access token and account id in a WebSocket message to henosis.us, which neither the README nor the PyPI description mentions.
henosis-cli is the terminal client for Henosis Chat, a multi-provider coding-agent service at henosis.us run by the package's publisher ("henosis", henosis@henosis.us). Among its routes is "use your Codex subscription": it sends OpenAI model requests with the user's own ChatGPT/Codex sign-in instead of Henosis credits. To do that, every start of the interactive client, before any prompt and with no flag, question or login command, reads the official Codex CLI's login file ~/.codex/auth.json if the client holds no Codex login of its own yet. It copies the OAuth tokens from that file (access token, refresh token, ID token, account id) into its own file ~/.henosis_cli_codex_auth.json, mode 0600. It does not copy an OPENAI_API_KEY stored in that file. In normal use the access token goes only to OpenAI: chatgpt.com for model requests and usage, api.openai.com for voice transcription. The refresh token goes only to auth.openai.com. Every other request the client makes to henosis.us, including its login check at startup, carries none of these tokens, at most a flag saying whether a Codex login is present. One command does send the Codex credential to the publisher's service. /codex transcribe <file.wav>, typed by the user while signed in to Henosis, opens a WebSocket to henosis.us/api_v2/realtime/transcription/ws and puts the ChatGPT access token and ChatGPT account id in its first message. The README and the PyPI description do not mention either the automatic import or this transfer, and the code's own comment on the Codex state says it is "never sent to Henosis backend". This is a credential exposure to a third party, opt-in per use but undisclosed. The code does not hide it and is not malware. The same code is in 0.6.205, 0.6.206, 0.6.207 and 0.6.208, all still on PyPI. The automatic import is in releases back to 0.6.21 (March 18, 2026). The transcribe transfer is in releases back to 0.6.93 (June 24, 2026).
What henosis-cli does
PyPI ships a Windows-only wheel (py3-none-win_amd64) for each of these releases, so on Linux and macOS pip installs henosis-cli from its source archive. Installing it runs no code of the package's own beyond setuptools' wheel tagging, and it adds no startup hook. When the user starts the client (henosis-cli with no arguments; henosis-cli --help exits before this), it does the following:
- If
~/.henosis_cli_codex_auth.json holds no usable Codex login, it reads ~/.codex/auth.json, the file the official Codex CLI writes when a user signs in with ChatGPT. If that file has a tokens object, the client takes access_token, refresh_token, id_token, account_id and last_refresh from it. It writes them, with the expiry and email decoded from the tokens, to ~/.henosis_cli_codex_auth.json through a temporary file beside it, then reads that copy back. It prints nothing about this. It never reads OPENAI_API_KEY or any other key from auth.json.
- It connects to henosis.us to check whether the user is signed in to Henosis (
GET /api_v2/check-auth with Henosis's own cookies from ~/.henosis_cli_auth.json). That request carries no Codex material.
- When the user picks an OpenAI model on the Codex route, the client sends requests to
https://chatgpt.com/backend-api/codex/responses, over HTTP or a WebSocket, with Authorization: Bearer <access token> and chatgpt-account-id. It presents itself to that endpoint as the Codex CLI (originator: codex_cli_rs, User-Agent: codex_cli_rs/0.159.0) and uses the Codex CLI's public OAuth client id. /codex usage and /codex reset read chatgpt.com/backend-api/wham/* with the same token.
- When the access token is near expiry, it posts the refresh token to
https://auth.openai.com/oauth/token. It then saves the new tokens to its own file and rewrites ~/.codex/auth.json with only auth_mode, tokens and last_refresh, so any other field the Codex CLI kept there is dropped. /codex logout deletes both files, which also signs the official Codex CLI out.
- Voice input (hold-to-talk and the Remote Control browser's dictation) sends audio with the Codex access token to
wss://api.openai.com/v1/realtime or https://api.openai.com/v1/audio/transcriptions.
/codex transcribe <24k-mono-pcm16.wav> first requires a Henosis login. It asks henosis.us for a WebSocket ticket (POST /api_v2/realtime/transcription/ws-token), opens wss://henosis.us/api_v2/realtime/transcription/ws?token=<ticket> with the Henosis cookies, and sends a start message holding "auth": {"mode": "codex_oauth", "access_token": <ChatGPT access token>, "account_id": <ChatGPT account id>, "originator": "henosis-cli"}, followed by the audio. Henosis's server then holds a working bearer token for the user's ChatGPT account until that token expires. The refresh token and ID token are not sent.
- Who receives it. henosis.us is registered to an individual in Utah, with no organisation named (Namecheap, since January 24, 2024). It resolves to a single address on a Comcast residential cable line in Utah (
73.20.52.255, c-73-20-52-255.hsd1.ut.comcast.net), served by nginx with a Let's Encrypt certificate and no cloud provider or CDN in front of it. No company, privacy policy, terms of service or statement on what the server does with the tokens it receives could be found: henosis.us shows none, and its /privacy, /terms and /legal paths serve the same app page as its home page. The token therefore goes to a home-hosted server run by one person, with no published policy covering it.
- It also uses other local logins in place, without copying them.
~/.claude/.credentials.json (Claude Code's login) is read to see whether a Claude subscription route is ready, and its token goes only to api.anthropic.com, with refreshes to platform.claude.com. ~/.grok/auth.json goes only to auth.x.ai and cli-chat-proxy.grok.com. Provider API keys the user enters are kept in ~/.henosis_cli_provider_keys.json and used with their own providers.
With Remote Control on (--agent-mode, or a terminal connected to henosis.us), the Henosis web pages (henosis.us/agent, henosis.us/mica) can start turns, usage checks and dictation that the local client then sends to OpenAI with the local Codex token. The token itself stays on the machine in those flows. What goes back through henosis.us is the conversation, readiness flags and usage counts, as the README's Remote Control and Mica sections describe.
What the code does
Version 0.6.208, henosis_cli-0.6.208.tar.gz (sha256 04e765365561947e8f148f3419b64ab0b51a322d6aa1cd57efb52381058a6c77), cli.py (55,175 lines, sha256 d5c0e7fa85d82ad4a98d97cf42cd67914ca2fdcda86a30e212339808ca4862b2):
cli.py:2773–2778: under the comment # Local Codex OAuth state (never sent to Henosis backend)., sets codex_auth_file = ~/.henosis_cli_codex_auth.json, codex_shared_auth_file = ~/.codex/auth.json, the token URL https://auth.openai.com/oauth/token and the responses URL https://chatgpt.com/backend-api/codex/responses. cli.py:2791 uses the Codex CLI's OAuth client id app_EMoamEEZ73f0CkXaXp7hrann by default.
cli.py:2890–2892, in the client's constructor: self._load_codex_auth_state_from_disk(), then if not self._has_codex_auth_material(): self._import_codex_auth_json_if_present(). Nothing asks the user or checks a setting first.
cli.py:13446–13478 _import_codex_auth_json_if_present reads ~/.codex/auth.json (line 13451), takes only tokens.access_token, tokens.refresh_token, tokens.account_id, tokens.id_token and last_refresh (lines 13454–13468), and calls _save_codex_auth_state_to_disk(sync_shared=False) (line 13475). cli.py:13233–13243 is the saved payload, which adds token_type, expiry_date and email. cli.py:13388–13397 writes it to ~/.henosis_cli_codex_auth.json with _write_json_private_atomic (cli.py:13273–13310: a mkstemp file beside it, chmod 0600, os.replace). cli.py:13312–13323 reads it back.
cli.py:16179–16235 refreshes: posts grant_type=refresh_token, the client id and the refresh token to self.codex_token_url (line 16207), then saves with sync_shared=True (line 16234). cli.py:13421–13444 _sync_codex_shared_auth_json overwrites ~/.codex/auth.json with {"auth_mode": "chatgpt", "tokens": {...}, "last_refresh": ...} and keeps no other field. cli.py:13399–13419 with clear_shared=True (called by /codex logout, line 25875) deletes both files.
cli.py:36285–36322 sets the model request headers: Authorization: Bearer {token}, originator, User-Agent: codex_cli_rs/…, chatgpt-account-id. cli.py:36460 streams to self.codex_responses_url, and cli.py:36023–36030 turns that same URL into wss://chatgpt.com/... for the WebSocket path. henosis_cli_tools/mica_runtime.py:1666–1682 sends background Mica turns to the same chatgpt.com endpoint with the same headers. henosis_cli_tools/codex_usage.py:20–24 and :237–240 send the token to chatgpt.com/backend-api/wham/*. cli.py:21110–21156 sends chatgpt.com a prompt-cache key that holds only a SHA-256 prefix of the account id.
cli.py:50926–50933 (realtime voice, wss://api.openai.com/v1/realtime) and cli.py:51074–51097 (https://api.openai.com/v1/audio/transcriptions) send the token to OpenAI.
cli.py:51367–51453 _codex_transcribe_command, reached from /codex transcribe at cli.py:25868–25870. Its docstring says it transcribes "through the backend realtime proxy". It requires a Henosis login (lines 51387–51391), gets the Codex access token (line 51393), and posts for a ticket to join_url(self.api_base, "/realtime/transcription/ws-token") (line 51404). It opens join_url(self.api_base, "/realtime/transcription/ws") (lines 51418–51436). At lines 51446–51452 it puts "auth": {"mode": "codex_oauth", "access_token": token, "account_id": self.codex_account_id, "originator": "henosis-cli"} in the start message, sent at line 51453. self.api_base comes from --server, whose default is os.getenv("HENOSIS_SERVER", "https://henosis.us/api_v2") (cli.py:835, cli.py:2560). This is the only place in the package where the access token goes into a request to the Henosis service. No path sends the refresh token or ID token anywhere but auth.openai.com.
henosis_cli_tools/access_adapters/claude_direct_oauth.py:35, :60 and :188–189 read ~/.claude/.credentials.json and use it only with api.anthropic.com and platform.claude.com. cli.py:2828–2832 reads ~/.grok/auth.json for cli-chat-proxy.grok.com and auth.x.ai.
README.md (also the PyPI description) says only "use your Codex subscription where supported". It does not say that the client reads the Codex CLI's login file on start, keeps a second copy of the tokens, rewrites or deletes ~/.codex/auth.json, or sends the access token to henosis.us for /codex transcribe. /codex status shows both file paths (cli.py:16515–16516). /codex transcribe is not in the client's slash-command list (cli.py:17286–17291), only in the /codex usage line (cli.py:25884). The package has no obfuscated code, and nothing runs at install or import.
The other three releases have the same code. In each, the bodies of _import_codex_auth_json_if_present and _codex_transcribe_command are byte-identical to 0.6.208's. 0.6.205 (a4b61e6a…cbed): constructor import cli.py:2874–2876, comment :2757, transcribe socket :51294, token in the start message :51325. 0.6.206 (b1fa010b…bf69): :2889–2891, :2772, :51402, :51433. 0.6.207 (cb5b349d…f660): :2889–2891, :2772, :51415, :51446. 0.6.206 adds codex_usage_contract.py and performance modules, and 0.6.207 adds codex_reset.py and codex_reset_contract.py. The only host any of them names is chatgpt.com. The automatic import first appears in 0.6.21 (it is not in 0.6.19), and the token in the transcribe start message first appears in 0.6.93 (not in 0.6.92). The releases checked between those and 0.6.205 carry both.
Timeline (UTC)
| When | What |
| 03/18/26 13:50:29 | henosis-cli 0.6.21 is published to PyPI; from this release the client imports ~/.codex/auth.json on start |
| 06/24/26 16:05:44 | henosis-cli 0.6.93 is published to PyPI; from this release /codex transcribe sends the Codex access token to henosis.us |
| 10/26/26 20:23:58 | henosis-cli 0.6.205 is published to PyPI (Windows wheel; sdist at 20:24:00) |
| 11/05/26 18:24:30 | henosis-cli 0.6.206 is published to PyPI (Windows wheel; sdist at 18:24:31) |
| 11/05/26 22:54:48 | henosis-cli 0.6.207 is published to PyPI (Windows wheel; sdist at 22:54:49) |
| 11/06/26 05:26:20 | henosis-cli 0.6.208 is published to PyPI (Windows wheel; sdist at 05:26:22) |
| 11/06/26 | PyPI still serves all four, none yanked. 0.6.208 has been up for less than a day; the automatic import has shipped for about seven and a half months, and the transcribe transfer for about four and a half |
If you installed it
- If you ever used
/codex transcribe while signed in to Henosis, your ChatGPT access token and account id went to henosis.us. Sign out of ChatGPT on all devices (or log out of Codex and sign in again) so that token stops working. Your refresh token did not leave your machine except to auth.openai.com.
- If you did not use
/codex transcribe, the Codex tokens went only to OpenAI. They are still copied in ~/.henosis_cli_codex_auth.json, readable by your user. Delete that file if you do not want Henosis to use your ChatGPT account. In these versions nothing turns the import off: the client imports again on its next start as long as ~/.codex/auth.json exists.
- Do not use
/codex logout just to stop Henosis using the account: it also deletes ~/.codex/auth.json and signs the official Codex CLI out.
- If you kept an API key or other settings in
~/.codex/auth.json, check that they are still there. Each time henosis-cli refreshes the Codex token, it rewrites that file with only the login tokens.
- Your Claude Code and Grok logins are used in place and only with Anthropic and xAI. Nothing needs rotating on their account.