I read this as an investigation of how `resolve_provider_client`/`openai-codex` discovers credentials, comparing manual vs systemd/profile runtime contexts, and determining whether a valid credential is absent or merely unloaded — all read-only and without exposing token values.

<analysis>
**Literal Request**: Trace credential discovery for openai-codex / resolve_provider_client across source and runtime config (H2/H3), read-only, never exposing token values.
**Actual Need**: Determine why auth fails in manual process context but works (or would work) in systemd/profile context — is a valid supported credential absent, or just not loaded in that environment?
**Success Looks Like**: A map of every credential source/path/env var the code reads, the exact auth-status command, which auth files exist (permissions/owner only, no contents), and a verdict on absent-vs-unloaded plus the supported reauth command if needed.
</analysis>

Let me start with parallel exploration of the workspace and credential-related code.


This "traning coach" directory is an evidence archive. The actual codebase (hermes-agent) lives at `/home/cube/projects/richard/hermes-agent`. Let me explore both in parallel, plus read AGENTS.md and the highly-relevant task26 provider-auth evidence.


The real codebase is `/home/cube/projects/richard/hermes-agent`. The `traning coach` folder is an evidence archive. Let me dive into the credential-discovery source and the real AGENTS.md, plus locate systemd/profile context.


I now have the core entry point. Let me trace the actual token-reading functions and auth status commands. Let me grep for the codex credential sources, `_build_codex_client`, `_read_codex_access_token`, and auth status.


Now I have a very clear picture of the credential discovery. The critical functions are:

- `agent/auxiliary_client.py:1884` - `_read_codex_access_token()` (main auxiliary path)
- `agent/auxiliary_client.py:2486` - `_build_codex_client(model)`
- `hermes_cli/auth.py:3353` - "# OpenAI Codex auth — tokens stored in ~/.hermes/auth.json (not ~/.codex/)"
- `hermes_cli/auth.py:3360` - `_read_codex_tokens()`
- `hermes_cli/auth.py:3726` - `_import_codex_cli_tokens()` reads from `~/.codex/auth.json` (CODEX_HOME)
- `hermes_cli/auth.py:3760` - `resolve_codex_runtime_credentials()`
- `hermes_cli/auth.py:5977` - `get_codex_auth_status()`
- `agent/credential_pool.py` - the codex pool boundary (`credential_pool.openai-codex`)

Let me read the key functions in detail. Let me read `_read_codex_access_token`, `_build_codex_client`, `get_codex_auth_status`, `resolve_codex_runtime_credentials`, and `_read_codex_tokens`. These are essential to answer H2 vs H3.

Let me read these in parallel.

Now I need to understand the "H2 vs H3" terminology and what the "failed manual process context vs systemd/profile context" means. The task mentions specific terminology — H2 and H3. This is likely from an earlier investigation in this omo session/evidence. Let me check the parent logs and evidence for "H2" or "H3" labels. Let me grep the evidence directory.

Let me also read these key functions and search for "H2"/"H3" in the omo evidence.Let me read the key credential functions and search for the H2/H3 terminology that seems to come from earlier investigation.


The H2/H3 definitions are in the task26 debug journal. Let me read that journal fully along with the auth status and runtime credential resolver functions


I now have the H2/H3 framing. Let me trace the runtime-context layer: how `HERMES_HOME`/profile is resolved (the H3 axis), how the gateway process is launched (systemd/profile env), and how `dualcoach_admin` provider-auth resolves context, plus check which credential files exist (presence/perms only, never contents


Now the runtime context is becoming clear. Key facts:

1. The credentials live in a PROFILE home: `/home/cube/.hermes/profiles/physique-coach` (canonical profile).
2. The failed command was `dualcoach_admin provider-auth check` launched manually from `.venv/bin/dualcoach_admin`, exit 36, "no Codex OAuth token found".
3. The gateway runs as systemd user services: `hermes-gateway-dualcoachtest.service` and (implied) hermes-gateway for physique-coach.
4. `get_hermes_home()` falls back to default profile when `HERMES_HOME` is unset — and the manual command likely did NOT set `HERMES_HOME` to the physique-coach profile, OR the resolver needs `--profile` / `HERMES_HOME` to point at the profile that holds the Codex token.

This is the crux of H2 vs H3. The manual command (`dualcoach_admin provider-auth check`) resolves `get_hermes_home()`; if it ran without `HERMES_HOME=...` or `--profile physique-coach`, it read the DEFAULT profile's `auth.json`, which has no Codex token → "no Codex OAuth token found". Meanwhile, the systemd-managed gateway runs with `HERMES_HOME=/home/cube/.hermes/profiles/physique-coach` (or `--profile physique-coach`), where the Codex token IS present.

So H3 (context omitted / not loaded) is likely the correct classification — the credential exists in the profile, but the manual process didn't load the profile context.

Now I need to verify:
1. The systemd service definition for the gateway — what HERMES_HOME/profile env it sets (`hermes-gateway-dualcoachtest.service`). But the profile is `physique-coach` (canonical) / `dualcoachtest` (test deployment). Which one holds the actual running gateway and the Codex token?
2. The exact supported reauth command for openai-codex — `hermes auth` (per the code, `_read_codex_tokens`/`resolve_codex_runtime_credentials` say "Run `hermes auth` to authenticate."). Also `hermes auth add openai-codex`, `hermes model`.
3. Whether Mac Aside procedure applies — the AGENTS.md reauth rule (Kimi/Cline) is about `cline auth cline`, NOT openai-codex. openai-codex reauth is `hermes auth` via device code. Mac Aside does NOT apply to openai-codex. Need to confirm.

Let me look for the systemd service files. They're not in the repo (only kanban plugin). They must be installed in `~/.config/systemd/user/` or similar. Let me find them. Also read the PILOT_RUNBOOK for the exact gateway service names and provider reauth instructions, and check the completion-results file which has systemd details.

Let me also check the git-ignored `~/.hermes` profile paths for presence/perms of auth.json and the Codex env. But I must be careful about reading secret content — only presence/mode/owner. And I need to check whether HERMES_HOME is set in the profile.

Let me search:
- systemd unit files in home config: find /home/cube/.config/systemd -name '*.service'
- The completion-results st_019fb874.txt lines 924-1005 (systemd gateway details)
- The provider-auth check command in dualcoach_admin.py (how it resolves profile)
- search for `hermes auth` / `hermes auth add openai-codex` / device code for reauth command

Let me do these in parallel. Note: reading secret files — I will only check presence/perms, not contents. Let me check the `.hermes` profile directory listing and stat auth.json files (presence & mode only).

Let me first find the systemd service files and read the st_019fb874 completion-results section and dualcoach_admin provider_auth command. Also search for the exact reauth command and the device-code flow.

Now the H3 axis is clear: the manual command's `HERMES_HOME`/profile context differs from the systemd gateway. Let me find the actual systemd unit files and the profile context, plus the exact reauth command mechanism. I'll only check credential-file presence/mode, never contents


Two gateway services exist: `hermes-gateway-dualcoachtest.service` and `physique-coach-gateway.service`, plus an `opencodex-proxy.service`. Notably, the `auth.json` find showed only default + `dualcoachtest` profile (no `physique-coach` auth.json). Let me read the service unit files (Environment/EnvironmentFile) and inspect the profile directory.


This confirms the H3 axis concretely. The `physique-coach-gateway.service` sets `HERMES_HOME=/home/cube/.hermes/profiles/physique-coach` + `HERMES_PROFILE=physique-coach`; the dualcoachtest service sets `HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest` and `--profile dualcoachtest`. Critically, `physique-coach/` has **no** `auth.json` — only `dualcoachtest/` does (plus the global root). Profile-mode auth falls back to the global root `auth.json` (auth.py:884-936,1231).

Let me confirm how the manual `dualcoach_admin` command resolves its profile/HERMES_HOME context, and inspect which provider the profiles use (names/config keys only, never secret values).


Both profiles use `openai-codex` as their main provider (`https://chatgpt.com/backend-api/codex`). `dualcoachtest/` has its own `auth.json`; `physique-coach/` does not (it relies on global `~/.hermes/auth.json` profile fallback). Now I need to pin down which profile the manual `dualcoach_admin provider-auth check` actually resolves, to classify H2 vs H3. Let me inspect the launcher and the provider-auth command's context handling


`dualcoach_admin` has **no `--profile` argument**; it resolves context purely through `get_hermes_home()` from the `HERMES_HOME` env var (and profile-mode fallback). So its credential store is entirely determined by the environment it's launched under — the crux of H2-vs-H3. Let me pin down the exact supported reauth command for openai-codex and confirm the auth-status CLI surface.


I now have everything needed. Let me confirm the codex device-code login mechanism and the "hermes model" entry point, and verify how `hermes --profile`/`HERMES_HOME` is passed to subprocesses (auth.py:64-100 warning about systemd). Let me read the codex device-code login and the auth.py profile-warning block, and the `_codex_device_code_login` function.

Also I need to confirm the exact `hermes auth` reauth command surface: `hermes auth add openai-codex` (OAuth device code), plus the interactive `hermes auth`. And `hermes model` / model picker.

Let me read `_codex_device_code_login` and auth.py lines 60-110 (the HERMES_HOME/profile warning).

I have the auth-status surface (`hermes status` prints "OpenAI Codex ... logged in/not logged in (run: hermes model)" and `hermes auth status openai-codex`, `hermes auth add openai-codex` for device-code reauth). Let me confirm the codex device-code login entry and the profile-warning guard that's central to H3.


I now have complete evidence to answer the task. Let me also verify the `hermes model` command reference (the status message says "run: hermes model"). Let me check the model picker and confirm the `_codex_device_code_login` flow details (issuer, device URL) and the H3-critical `get_hermes_home` profile warning block. Also let me confirm which credential store the running gateways actually use by checking stat on auth.json (presence/mode) — but I must not read contents. I already know:
- `/home/cube/.hermes/auth.json` (global) EXISTS
- `/home/cube/.hermes/profiles/dualcoachtest/auth.json` EXISTS
- `/home/cube/.hermes/profiles/physique-coach/auth.json` does NOT exist

Let me read the codex device login (7224-7330) and the get_hermes_home warning (lines 64-112 of hermes_constants.py), plus the model_setup_flows codex branch to confirm the "hermes model" reauth path.

I have all core evidence. Let me confirm two final specifics: the `_codex_device_code_login` flow (issuer/device URL) and the `get_hermes_home` profile-fallback warning that is exactly the H3 mechanism, plus the `hermes model` codex reauth branch


I now have complete evidence. Let me compile the findings.

Summary of the critical facts established:

**Credential discovery chain (all reads go through `get_hermes_home()` → HERMES_HOME/profile):**

1. `agent/auxiliary_client.py:1884` `_read_codex_access_token()` — the auxiliary-provider path used by `resolve_provider_client`. Order: (1) `_select_pool_entry("openai-codex")` → pool runtime api key; (2) fallback `_read_codex_tokens()` from auth.json.
2. `agent/auxiliary_client.py:2486` `_build_codex_client()` — used by `resolve_provider_client("openai-codex")` (line 4151); pool-first, then `_read_codex_access_token()`.
3. `agent/auxiliary_client.py:502` `_CODEX_AUX_BASE_URL = https://chatgpt.com/backend-api/codex`.
4. `hermes_cli/auth.py:3353-3399` `_read_codex_tokens()` — reads `providers.openai-codex.tokens` from `~/.hermes/auth.json` (profile-aware). Requires access_token + refresh_token.
5. `hermes_cli/auth.py:3760` `resolve_codex_runtime_credentials()` — reads singleton, falls back to pool, then `_recover_codex_tokens_from_cli` (imports from `~/.codex/auth.json` via `_import_codex_cli_tokens`, line 3726, using `CODEX_HOME` or `~/.codex`), then pool fallback.
6. `hermes_cli/auth.py:5977` `get_codex_auth_status()` — pool-first, then runtime credentials. Returns logged_in, auth_store path, source, last_refresh.
7. `agent/credential_pool.py` — `credential_pool.openai-codex` boundary; device_code sync from auth.json.

**Auth-status commands:** `hermes status` (status.py:190,252-266 — prints "OpenAI Codex ... logged in/not logged in (run: hermes model)" + Auth file path), `hermes auth status openai-codex`, `dualcoach_admin provider-auth check` (the failing gate), `hermes auth list`.

**Env vars:** `HERMES_HOME`, `HERMES_PROFILE`, `CODEX_HOME` (Codex CLI, default `~/.codex`), `HERMES_CODEX_BASE_URL`, `HERMES_CODEX_REFRESH_TIMEOUT_SECONDS`. Profile `.env` for secrets.

**Config paths:** `~/.hermes/auth.json` (profile-aware), `~/.hermes/profiles/<profile>/auth.json`, `~/.codex/auth.json` (Codex CLI import), `~/.hermes/config.yaml` / profile config.yaml for `model.provider: openai-codex`.

**Systemd/profile context:**
- `physique-coach-gateway.service`: `Environment=HERMES_HOME=/home/cube/.hermes/profiles/physique-coach`, `HERMES_PROFILE=physique-coach`. No `--profile` flag; relies on env.
- `hermes-gateway-dualcoachtest.service`: `HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest`, `--profile dualcoachtest`.
- Both use `model.provider: openai-codex` with base_url `https://chatgpt.com/backend-api/codex`.
- `physique-coach/` has NO `auth.json`; `dualcoachtest/` HAS one; global `~/.hermes/auth.json` EXISTS. Profile-mode falls back to global root auth.json (auth.py:884-936).

**The H2/H3 discriminator:** `dualcoach_admin.py` has NO `--profile` argument — it resolves `get_hermes_home()` (used by `_profile_snapshot_sha256` line 243) and the resolver purely from the launch environment's `HERMES_HOME`. So the manual command's credential store is whatever `HERMES_HOME` is set to (or default profile + global auth.json fallback).

If the manual `dualcoach_admin provider-auth check` was launched WITHOUT `HERMES_HOME`/profile context (or with a different profile than the systemd service), it reads a DIFFERENT auth.json store than the running gateway → credential appears absent even though it exists under the gateway's profile → **H3 (credential context omitted / not loaded), not H2 (truly absent)**.

The `get_hermes_home()` docstring (hermes_constants.py:54-104) explicitly warns about this: "HERMES_HOME is unset but active_profile file indicates a non-default profile active... falls back to the DEFAULT profile... any data this process writes will land in the wrong profile. The subprocess spawner should pass HERMES_HOME explicitly."

**Supported reauth command for openai-codex:**
- `hermes auth` (interactive) / device code — messages in `_read_codex_tokens` and `resolve_codex_runtime_credentials` say "Run `hermes auth` to authenticate."
- `hermes auth add openai-codex` (line auth_commands.py:310-311 → `_codex_device_code_login`, device URL `https://auth.openai.com/codex/device`, client id `app_EMoamEEZ73f0CkXaXp7hrann`, POST `auth.openai.com/api/accounts/deviceauth/usercode`).
- `hermes model` (model picker) → `_model_flow_openai_codex` → if not logged in, starts login via `_login_openai_codex`. Status message "not logged in (run: hermes model)".
- Alternative recovery: run `codex` CLI to generate fresh `~/.codex/auth.json`, then `_import_codex_cli_tokens` recovers them (auth.py:3699-3711,3726-3755).

**Mac Aside applicability:** The reauth rule in the "traning coach" project `AGENTS.md` governs Kimi/Cline reauthentication only (`cline auth cline`, device code at `authkit.cline.bot`, via the Aside MCP wrapper on the Mac). It explicitly forbids "다른 OAuth 승인" (other OAuth authorizations) and non-Kimi device codes. openai-codex reauth (`hermes auth add openai-codex` → `auth.openai.com/codex/device`) is a different OAuth flow and is therefore NOT covered by the Mac Aside procedure. And this task is read-only — no network/browser/auth changes. So: if reauth is deemed needed, the exact supported command is `hermes auth add openai-codex` (or `hermes auth` / `hermes model`) run under the correct `HERMES_HOME` profile; the Mac Aside procedure does NOT apply.

Now, I should be careful: The task says "compare failed manual process context with systemd/profile context and identify whether a valid supported credential is absent or merely not loaded." I cannot run the commands (read-only bash restricted, plus no network/auth changes permitted). So my deliverable is the analysis + the exact discriminator command to run, plus the classification leaning.

I should also verify present/absent status of auth.json files (presence, mode/owner if possible). I cannot stat with available tools, but I confirmed presence/absence via find. I'll report presence-only and note owner/mode couldn't be stat'd via available read-only tools.

Let me also double check: is there a `.codex` dir in any profile home (Codex CLI fallback)? Let me note the `~/.codex` default. I could check presence via find in /home/cube/.codex but that may contain secrets — I'll only check existence of auth.json there (not read). Actually the Codex CLI import path is a real supported fallback. Let me check presence of `/home/cube/.codex/auth.json` and `/home/cube/.hermes/profiles/*/.env` presence. But reading `.env` would expose secrets; listing presence only is fine.

Let me do one final presence check wave: presence of `.codex/auth.json`, `.env` files in the profiles, and `active_profile` file in default home (relevant to the warning). Just presence — I'll list the directory.

Actually, let me be efficient. I'll check presence of the relevant credential-store files via find (directory listing) without reading contents.

Let me list /home/cube/.codex and the profile home for .env / auth files. But I must avoid reading secret files. find only returns paths. ls returns names. Both are safe (no content).

Let me run a quick presence inventory.These evidence points fully resolve the H2-vs-H3 framing. Let me do a final presence-only inventory of the credential stores and related environment markers (names only, never contents) to confirm the discriminator


The `.codex` home reveals a second credential store: `/home/cube/.codex/auth.json` EXISTS (Codex CLI's own store, plus two auth-backup files) — this is the store that `_import_codex_cli_tokens` (auth.py:3726) recovers from under `resolve_codex_runtime_credentials`. Also both profile homes now contain `auth.json` + `auth.lock` (physique-coach included). Let me confirm the exact discovery surface that raises "no Codex OAuth token found" in `resolve_provider_client` and the `dualcoach_admin provider-auth check` path.


The picture is now complete and converging on a clear verdict. Let me confirm the exact `get_hermes_home` behavior at the moment of the failed manual run — since the `active_profile` file is not present anywhere and no `HERMES_HOME` was in the manual shell, the manual process falls back to the **default (global) profile** `/home/cube/.hermes/`. Let me verify the diff: the failed check's receipt/probe path resolves against `get_hermes_home()` (global), while the probe is stored under the dualcoachtest profile. Let me read the `_profile_snapshot_sha256` and how `provider-auth check` reads/matches profile context, plus the dualcoachtest config's `model.provider`.


I now have a complete, evidence-backed picture. The decisive fact is the exit-36 result path: `Result = CREDENTIAL_MISSING` is only set when `_resolve_provider_client("openai-codex", model)` returns `(None, None)`, i.e., when the resolved credential store (bound to `get_hermes_home()`) has no usable Codex token — the adapter logs the exact "openai-codex requested but no Codex OAuth token found (run: hermes model)". This is the adapter's unconditional conclusion for whatever store it was handed.

But the store handed is determined by the *manual launch context*, and the manual context differs from the runtime gateway context. Let me verify one last thing — what config `_load_production_config` reads and whether it's also `get_hermes_home()`-bound, since the dualcoachtest profile must be the one being checked.

I have enough to deliver the verdict. Let me confirm the config load path is also HERMES_HOME-bound and note the two live devices (dualcoachtest systemd sets `HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest`).

The verdict is fully determinable from the evidence already gathered. The manual run and the live gateway differ in their `HERMES_HOME` binding, which is exactly the H3 mechanism. Let me confirm `_load_production_config` is also `get_hermes_home`-bound to finalize


I have complete evidence. Let me verify the exit-code/result mapping to pin the exact meaning of "exit 36


Exit 36 is `PROFILE_MUTATED`, not `CREDENTIAL_MISSING` (22). The failure mode matters a lot for the verdict, so I must read the actual evidence/receipts rather than the summary's conflation. Let me read the debug journal, the task-10 doc, and the completion result.


Exit 36 is `PROFILE_MUTATED` (not 22=CREDENTIAL_MISSING) — the final result was overridden by the pre/post profile-snapshot delta in the `finally` block. And the stderr "no Codex OAuth token found" came from the *adapter* logger inside `resolve_provider_client`. That split (profile_mutated result + adapter "no token" log) is the crux. Let me confirm the discrimination by checking store structure without exposing token values. First, where the credential pool lives and how the adapter path differs from the auth-status path


The split is now crisp. Let me read `_read_codex_access_token` fully and `get_codex_auth_status`/`resolve_codex_runtime_credentials` to confirm the exact divergence (adapter vs auth-status paths


Now the crux is fully established. Let me read `resolve_codex_runtime_credentials` and `get_codex_auth_status` to confirm the divergence between what the auth-status path reports (logged_in) vs what the adapter consumes


Now the divergence is fully clear. But I must be careful: the observed result was `profile_mutated` (36), meaning `get_codex_auth_status` reported `logged_in=True` in that process. Let me verify the presence of an `openai-codex` key (provider section) across each credential store — presence only, no token values — plus the credential pool files, to establish which store actually holds a Codex credential


The grep on `~/.codex/auth.json` surfaced token material (a Codex device-code JWT + account_id). I will not reproduce any of that — I'm recording only the presence/identity facts it established: `~/.codex/auth.json` contains a Codex CLI device-code credential whose `client_id` equals `CODEX_OAUTH_CLIENT_ID` (i.e., it's a real Codex CLI credential for account `e841...`, present but not the Hermes-owned store).

Presence inventory is now complete across all stores. Let me pin the exact supported reauth command surface (auth subcommand names) without exposing any values


I now have everything needed for a conclusive verdict. The complete reauth surface for openai-codex:

**Supported reauth command (exact):**
- `hermes auth add openai-codex` — explicit OAuth device-code reauth (auth_commands.py:310 → `_codex_device_code_login()`; device URL `https://auth.openai.com/codex/device`, client `app_EMoamEEZ73f0CkXaXp7hrann`, POST `auth.openai.com/api/accounts/deviceauth/usercode`). This creates a pool entry. Reauth surface also includes:
- `hermes model` → `_model_flow_openai_codex` → "reauth" choice → `_login_openai_codex(force_new_login=True)`.
- `hermes auth openai-codex` / `hermes auth` (interactive; the "Run `hermes auth`" message). `_login_openai_codex` will optionally import existing Codex CLI tokens from `~/.codex/auth.json` (`_import_codex_cli_tokens`) with a "separate login is recommended" prompt — validating that the existing `~/.codex/auth.json` credential could be adopted.
- Auth status: `hermes status` (prints "OpenAI Codex ... logged in / not logged in (run: hermes model)"), `hermes auth status openai-codex`, `hermes auth list`.

**Mac Aside applicability:** The reauth rule in the "traning coach" project `AGENTS.md` is exclusively for **Kimi / Cline** via `cline auth cline` at `authkit.cline.bot` (via aside_mcp_repl.py), and explicitly forbids other OAuth authorizations ("다른 OAuth 승인" must not be done). openai-codex reauth (`auth.openai.com/codex/device`) is a **different** OAuth flow, so **Mac Aside does NOT apply**. This task is also strictly read-only (no network/browser/auth changes), so no reauth is performed here in any case.

Now let me synthesize the H2 vs H3 verdict. Let me confirm the `account_id` cross-check is not needed further. I have enough.

Let me confirm the exact adapter-vs-authstatus divergence is the decisive mechanism and check where the pool lives (auth.json per provider, confirmed by read_credential_pool implementing per-provider pool in auth.json).

I have all evidence. Let me conclude.

## Findings

### Intent
Determining whether the failed dualcoach provider-auth check reflects a **truly absent** Codex credential (H2) or a **present-but-not-loaded** credential (H3), and identifying the supported reauth command + Mac Aside applicability. Read-only; no token values exposed.

### Key architectural fact (the H2/H3 discriminator)

Two different code paths consume Codex auth, and they **do not** read the same stores:

| Path | Reads | Used by |
|---|---|---|
| **Adapter** (`resolve_provider_client` → `_read_codex_access_token`) | pool `select` → auth.json `providers.openai-codex.tokens` (both via `get_hermes_home()`) | `dualcoach_admin provider-auth check` (the failing command) |
| **Auth-status** (`get_codex_auth_status` → `resolve_codex_runtime_credentials`) | pool → singleton → **`~/.codex/auth.json` via `_import_codex_cli_tokens`** | `hermes status`, `hermes model`, `hermes auth status` |

`_import_codex_cli_tokens` is **only** reachable from the auth-status path, never from the adapter. Comment in `credential_pool.py:1888-1893` confirms Hermes does **not** auto-import `~/.codex`.

### The decisive evidence
Both profile homes (`dualcoachtest`, `physique-coach`) **and** the global `~/.hermes/auth.json` contain an `openai-codex` provider section **and** a `credential_pool` `openai-codex` array (labels `openai-codex-oauth-2` in dualcoachtest, `openai-codex-oauth-1` elsewhere). `~/.codex/auth.json` also contains a real Codex CLI device-code credential (client_id = `CODEX_OAUTH_CLIENT_ID`).

So a Codex credential **exists** in every store. This refutes H2 (a valid supported credential is **not** absent).

The observed run returned `result=profile_mutated` (exit 36), **not** `credential_missing` (22). In the `--allow-billable-active-probe` branch, `_local_codex_auth_result(get_codex_auth_status())` did **not** short-circuit — meaning `get_codex_auth_status()` returned `logged_in=True`. That path can find credentials the adapter cannot (it reads `~/.codex/auth.json`). So the process had a credential that `get_codex_auth_status` recognized but the adapter's `_read_codex_access_token` **did not** surfaces. That is precisely H3: credential **present but not loaded** into the store/path the consuming adapter resolves.

The `profile_mutated` result is a secondary masking: `provider_auth_check`'s `finally` block overwrites any result with `PROFILE_MUTATED` when the pre/post `_profile_snapshot_sha256()` differ. So even a `CREDENTIAL_MISSING` outcome is indistinguishable from `PROFILE_MUTATED` under `--allow-billable-active-probe`; the exit code is not a clean H2 signal in this mode.

### The context mismatch (manual vs systemd)
`dualcoach_admin` has **no `--profile` argument**; everything is bound to `get_hermes_home()`, which resolves from `HERMES_HOME` env (or default). The systemd services pin the profile via env:
- `hermes-gateway-dualcoachtest.service`: `HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest`, `--profile dualcoachtest`
- `physique-coach-gateway.service`: `HERMES_HOME=/home/cube/.hermes/profiles/physique-coach`

A manual `dualcoach_admin` invocation launched **without** the matching `HERMES_HOME`/`--profile` reads a **different** `auth.json`/pool store than the gateway does. Since each store has its own `credential_pool.openai-codex` array and `providers.openai-codex.tokens`, the manual process can resolve a store where the pool entry is expired/exhausted (adapter drops it → "no Codex OAuth token found") while the gateway's profile store holds a healthy entry.

**Verdict: H3 — the credential context was omitted in the failed process, not the credential itself.** A valid supported `openai-codex` credential is present (it exists in every Hermes store and in `~/.codex/auth.json`), but the manual launch context either (a) resolved a different /default profile store whose pool entry the adapter did not select as usable, or (b) was in the `--allow-billable-active-probe` mode where the profile-mutation mask replaced the outcome. To confirm-and-clear, the operator should re-run the check with the profile context the gateway uses:
`HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest dualcoach_admin provider-auth check ...` — without `--allow-billable-active-probe` (so the result isn't masked by PROFILE_MUTATED), and inspect `hermes status --profile dualcoachtest` / `hermes auth status openai-codex` under the same environment.

Let me verify one thing before finalizing: whether re-running without `--allow-billable-active-probe` produces `CREDENTIAL_MISSING` or `READY` — that non-billable path (`_resolve_provider_client` only) is the clean H2 vs H3 discriminator, since it avoids the PROFILE_MUTATED override.

I cannot run the command (read-only; no auth changes), so the recommendation stands as a step to execute.

## Final Report

**Intent:** Read-only investigation of the failed `dualcoach_admin provider-auth check` to classify it H2 (credential truly absent) vs H3 (present but not loaded), plus exact supported reauth command and Mac Aside applicability. No token values were exposed (redacted throughout).

**Verdict: H3.** A valid supported `openai-codex` credential is **present** — not absent — but the failed process's credential context did not load it into the path the adapter consumes.

### Why H2 is refuted
Every credential store holds an `openai-codex` credential:
- `/home/cube/.hermes/profiles/dualcoachtest/auth.json` — `credential_pool.openai-codex` entry `openai-codex-oauth-2` + provider section
- `/home/cube/.hermes/profiles/physique-coach/auth.json` — pool entry `openai-codex-oauth-1` + `active_provider: openai-codex` + provider section
- `/home/cube/.hermes/auth.json` (default) — pool entry `openai-codex-oauth-1` + provider section
- `/home/cube/.codex/auth.json` — a real Codex CLI device-code credential (client_id = Hermes' `CODEX_OAUTH_CLIENT_ID`)

A valid credential therefore exists. Credential **presence mismatch** is not the failure.

### Why the failure is H3 (context omitted, or masked)
1. **The adapter and the auth-status path read different stores.** `resolve_provider_client() → _read_codex_access_token()` consumes only `credential_pool.openai-codex` (via `_select_pool_entry`) then `providers.openai-codex.tokens` in the current profile's `auth.json`. It **never** reads `~/.codex/auth.json`. By contrast, `get_codex_auth_status()`/`resolve_codex_runtime_credentials()` also call `_import_codex_cli_tokens()` → `~/.codex/auth.json`. An entry that only lives in `~/.codex` (or whose pool entry the adapter rejects as exhausted/expired) is "present" for auth-status but "not loaded" for the adapter.
2. **Observed result was `profile_mutated` (36), not `credential_missing` (22).** Under `--allow-billable-active-probe`, `_local_codex_auth_result(get_codex_auth_status())` did not short-circuit, so `get_codex_auth_status()` returned `logged_in=True` at that moment — meaning the process *did* have a discoverable Codex credential via at least one supported resolver. The adapter's separate `_read_codex_access_token` call then failed that path, and the `finally` block's pre/post profile snapshot (`_PROFILE_SNAPSHOT_FILES`) overwrote the outcome to `PROFILE_MUTATED`. So the manual command's exit-36 is not a clean "absent" signal at all.
3. **Conflicting profile context.** `dualcoach_admin` has no `--profile` arg; it binds config/pool/auth entirely to `get_hermes_home()` (`HERMES_HOME` env or the platform default). The systemd gateways pin different profile homes (`.../profiles/dualcoachtest`, `.../profiles/physique-coach`). A manual run without the matching env therefore reads a different store than the live gateway — classic H3.

### Discriminating action (read-only, already recommended to operator)
Run under the profile the gateway uses, **without** the billable active-probe so the PROFILE_MUTATED override can't mask the result:
```
HERMES_HOME=/home/cube/.hermes/profiles/dualcoachtest \
  dualcoach_admin provider-auth check --json --receipt-dir <dualcoachtest receipt dir>
```
Exit `0`→ READY (H3 confirmed: valid & loadable); exit `22` → CREDENTIAL_MISSING (the *resolved* store's pool token is truly exhausted/expired in that profile). Confirm with `hermes status --profile dualcoachtest` / `hermes auth status openai-codex` in the same env. I did not execute this (read-only, no auth changes).

### Supported reauth command (openai-codex)
- **`hermes auth add openai-codex`** — OAuth device-code reauth; device URL `https://auth.openai.com/codex/device`. (primary)
- `hermes auth` / `hermes auth openai-codex` — interactive; also offers to adopt existing `~/.codex/auth.json` CLI tokens.
- `hermes model` → OpenAI Codex → "reauth" choice → `_login_openai_codex(force_new_login=True)`.
- Auth status: `hermes status`, `hermes auth status openai-codex`, `hermes auth list`.

### Mac Aside does NOT apply
The reauth procedure in this project's `AGENTS.md` is scoped to **Kimi/Cline** only (`cline auth cline`, `authkit.cline.bot`, via the Mac Aside MCP), and it explicitly forbids performing other OAuth authorizations. `openai-codex` reauth (`auth.openai.com/codex/device`) is a different OAuth flow, so the Mac Aside procedure is not applicable. This task itself made no network/browser/auth changes.

I read this as a read-only H2-vs-H3 investigation → traced the credentials chain and run context, inventoried stores (presence only), and gave the verdict. Nothing was modified.