Repository navigation
Description
[Bug] setup --json reports loggedIn:true, verified:true but rescue/agent invocation fails with refresh token already used
Summary
codex-companion.mjs setup --json advisory consistently reports the auth as
healthy (auth.loggedIn:true, auth.verified:true, ready:true), but the very
next rescue-agent invocation (Agent({subagent_type:"opnd-codex:codex-rescue"}))
fails with Your access token could not be refreshed because your refresh token was already used. and never reaches Codex.
Re-running codex logout && codex login (or re-authenticating in Codex
Desktop) makes the next setup --json succeed again, but the very next
rescue-agent invocation still fails with the same refresh token already used
error. Observed 3 consecutive cycles of (re-auth → setup verified → rescue
fail) in a single session.
This makes the setup verification effectively a false-positive advisory:
the host sees green light, retries the work, and burns a Codex session every
time.
Environment
| Field | Value |
|---|---|
| Plugin | opnd-io/opnd-codex-plugin (this repo) |
| Plugin version | 2.1.0 (or latest Unreleased per CHANGELOG, observed locally at plugins/opnd-codex/ HEAD) |
| Codex CLI | codex-cli 0.130.0; advanced runtime available |
| Auth source | app-server (ChatGPT, authMethod: chatgpt) |
| Account | ChatGPT subscription |
| Host | Windows 11 Pro 10.0.26200 (PowerShell + Git Bash) |
| Claude Code | Opus 4.7 harness |
| Locale mitigation | plugin spawns codex with LANG/LC_ALL=en_US.UTF-8 (non-UTF-8 host locale notice fired) |
| sessionRuntime mode | observed both shared (pipe IPC) and direct between sessions; no difference in outcome |
Reproduction
- From Claude Code, run
/opnd-codex:setup. Result:{ "ready": true, "codex": { "available": true, "detail": "codex-cli 0.130.0; advanced runtime available" }, "auth": { "available": true, "loggedIn": true, "verified": true, "source": "app-server", "authMethod": "chatgpt" } } - From Claude Code, spawn a rescue agent (background or foreground) — e.g.
Agent({subagent_type:"opnd-codex:codex-rescue", prompt:"<anything>"}). - Agent returns:
[codex-rescue] Codex was not invoked — the Bash call failed or was denied. Codex 인증 토큰이 만료되어 있습니다 (refresh token already used). - User runs
codex logout && codex login(or re-authenticates in Codex
Desktop).setup --jsonreports green again. - Spawn rescue agent again — same
refresh token already usedfailure. - Repeat 3 cycles. Same outcome each time.
Expected vs Actual
- Expected:
setup --json verified:truepredicts that the next
rescue-agent invocation can actually start a Codex session. - Actual: setup probes a different code path (auth cache / token presence)
than the rescue agent uses (new session start → uses refresh token →
reuse-detection error). The two paths disagree, so the green light is
misleading.
Impact
- Users waste cycles on
codex logout && codex loginthinking the host harness
needs a nudge, then the very next call still fails. - Plugin tools that gate themselves on
setup verified:true(e.g. router
default-pair, skip-taxonomyfalse_positive_advisoryreason) end up
recommending Codex pair when the actual call cannot succeed. - For me specifically: a 5-turn investigation thread ran 3 retries before
giving up; verification looked healthy each turn, so the harness kept trying.
Hypothesis
- ChatGPT-auth refresh tokens appear to be single-use / rotated in
codex-cli 0.130.0.setuphappily reads the cache while it is still valid
in the file system, but the moment the rescue agent triggers a fresh
app-server session start, the refresh token has already been consumed by an
earlier process (Codex Desktop, an earlier plugin call, or an outdated cache
entry) and rejected. - The
setup --json verifiedcheck needs to either:- actually issue a lightweight ChatGPT app-server probe that consumes a
refresh-cycle the same way the rescue agent will, or - treat the auth cache as advisory only and downgrade
verified:trueto a
softer signal (e.g.loggedIn:true, verification:cached-only).
- actually issue a lightweight ChatGPT app-server probe that consumes a
Suggested next steps for maintainer
- Reproduce on a Windows + Git Bash + ChatGPT-auth host (the locale notice
suggests this combination is already known). - Decide whether
setup --jsonshould perform a real app-server round trip
before declaringverified:true, or whether theverifiedfield should
be removed in favor of a softer signal. - Document the failure mode in CHANGELOG / README so harness authors can
choose a non-Codex fallback when 2+ recent rescue invocations have failed
withrefresh token already usedin the same session.
Related logs
- 3 consecutive failures captured locally; trace IDs available on request.
- All failures emitted the same string:
Your access token could not be refreshed because your refresh token was already used. - No
actionsTakenreported bysetup --jsonbetween the 3 cycles.
(이 파일은 사용자가 opnd-io/opnd-codex-plugin 의 이슈 기능을 활성화한 뒤
gh issue create --body-file <이 파일> --title ... 로 등록할 수 있도록 작성된
draft. 한글 + 영문 혼용 — 사용자 환경에 맞춰 가다듬어도 됨.)
Activity
tgkim-openerd commentedon May 28, 2026
Additional reproduction — single-use refresh token rotation hypothesis confirmed (2026-05-28, follow-up)
Captured a clean back-to-back trace in the same Claude Code session that confirms
the hypothesis from the original report (
setup --jsonadvisory is a cachedview of an already-rotated refresh token).
Sequence
opnd-codex:setup --json(after the user re-authenticated):Codex 연결 헬스 체크 / OK 만 출력하라):tool_uses:0, duration_ms:4150. Codex replied withOK / ....Your access token could not be refreshed because your refresh token was already used.opnd-codex:setup --jsonagain, immediately after the fail:What this shows
rotation policy on the OpenAI side).
already-rotated token → reuse detection → 401.
setup --jsoncorrectly reflects the broken state only aftera failed call rolls the cache (step 4 reports
loggedIn:falsehonestly).In step 1,
setup --jsonread the cached "fresh after login" token anddeclared
verified:true, but had no way to know the next rescue invocationwould consume it.
sessionRuntime.modealso flips:direct→sharedonce the firstcall leaves a pipe behind. The shared pipe survives the auth failure, so
subsequent calls hit the dead pipe (issue [Bug] setup --json reports verified:true but rescue agent fails with 'refresh token already used' (false-positive advisory) #2 step 5 pattern).
What the host harness saw
Same as issue #2: the harness saw
verified:true, started background work,got the same
refresh token already usederror, and had no signal in advancethat the 2nd call would fail. Setup advisory remains a false-positive predictor.
Suggested fix direction
setup --jsonshould either:verified:true(consuming the refresh token slot the same way a real rescue call would),
verified:trueentirely whenauthMethod=chatgpt,and instead emit
verified:cachedso harness authors know the green lightis advisory only.
with
refresh token already used, the pipe is torn down and the nextsetup --jsondoesn't keep dangling a dead pipe insessionRuntime.endpoint.(이 텍스트는 #2 코멘트로 그대로 붙여 쓸 수 있도록 영문 위주로 작성.)
fix(daily-evolve): Phase 1.5a Codex pair review R1-R2 finding 적용 (0 수렴)
feat(daily-evolve): self-evolution pipeline (Phase 0~6 + 1.5a + budge…
docs: /analyze Recommendations + /doc-sync apply + multi-round PII re…
feat(telemetry-ux+setup): Phase A — auth expired/usage-limit + plugin…
docs(exploration): Sprint 4-8 implementation guide — substrate critic…
tgkim-openerd commentedon Jun 8, 2026
Root cause confirmed: v2.0.0 dual-home auth drift (not just a probe mismatch)
Tracked this down on plugin 2.2.0 / codex-cli 0.134.0 / Opus 4.8 harness / Windows 11. The "setup green but rescue fails with
refresh token already used" symptom is a direct consequence of the v2.0.0 home split, and it's fully reproducible from the on-disk state.What actually happens
buildPluginCodexEnv()inscripts/lib/app-server.mjs(PR-5.6 / openai#282) injects a dedicated home into every plugin-spawned codex child:So there are two independent auth.json files:
~/.codex/auth.jsoncodexCLI, Codex Desktop, andsetup --json's verified checkcodex login/ Desktop re-auth~/.codex/claude-code/auth.jsonbuildPluginCodexEnv→CODEX_HOME)When the user re-authenticates (
codex logout && codex loginor Desktop), only the root~/.codex/auth.jsonrotates its refresh_token. The plugin-home copy still holds the previous refresh_token. The next rescue agent spawns codex withCODEX_HOME=~/.codex/claude-code/, tries to refresh using that now-superseded token, and ChatGPT's reuse-detection rejects it →refresh token already used. Meanwhilesetup --jsonkeeps reportingverified:truebecause it probes the root home, which is fine. That's the "false-positive advisory" in this issue — the two code paths read different homes, not just different cache layers.This also explains the infinite re-auth loop: every
codex loginonly fixes root; the plugin home stays stale, so the very next rescue fails identically.Evidence (the plugin's own advisory already detects it)
setup --jsonreturns:855305s ≈ 9.9 days, matching the two file mtimes exactly (rootauth.json~9.9 days newer than~/.codex/claude-code/auth.json). So the data to predict the failure is already computed — it just isn't wired into theauth.verifiedverdict.Suggested fixes (in priority order)
setup --jsonverified check probe the same home the rescue path uses (buildPluginCodexEnv). This is the actual ask of this issue — stop the green light when the plugin home token is the one that will be used and is stale. Cheap version: foldpluginHomeAdvisory.staleAuthintoauth.verified(verified:false when staleAuth and not USE_DEFAULT_HOME).staleAuth); or on arefresh token already usederror, re-copy root auth and retry once.CODEX_PLUGIN_USE_DEFAULT_HOME=1collapses both homes back to~/.codex/, so there's only one token and no drift. Caveat worth documenting: it must be present in the env Claude Code hands to the broker — on Windows a freshly-setx/user-scope env var is not picked up by a mere VS Code window reload (the parent process tree keeps the old block); it needs a full sign-out/in, or set it via Claude Codesettings.json"env"which is injected directly into spawned processes.Workarounds for anyone hitting this now
cp ~/.codex/auth.json ~/.codex/claude-code/auth.json(the companion's ownnextSteps[0]). Unblocks the next rescue, but re-drifts on the next token rotation.CODEX_PLUGIN_USE_DEFAULT_HOME=1viasettings.json"env"(then restart Claude Code so the broker re-spawns with it).Happy to PR fix #1 (folding
staleAuthinto theverifiedverdict) if that direction is acceptable.docs(claude-md): reflect v2.2.1 staleAuth self-detection in plugin-ho…