v0.5.17
v0.5.17 — AI provider failure modes: real-user "Not logged in" leak, codex version trap, /reset recovery
Date: 2026-05-11
Tag: v0.5.17 + desktop-v0.5.17
Scope: Patch release driven by a single user-reported screenshot — the WeChat bot was replying with the literal string "Not logged in · Please run /login" as if it were the AI's answer. Excavation surfaced two distinct failure modes plus the absence of any user-facing recovery affordance. Both modes now have structural fixes; users get a recovery surface for the residual cases.
Predecessor: v0.5.16 — four real-user reports, four surgical fixes
TL;DR
Three classes of failure addressed, plus the missing recovery surface:
- claude provider leaks the OAuth-stale sentinel as a chat reply — long-running stream-json subprocess holds tokens from startup, the user's interactive
clauderotates keychain credentials underneath, the daemon's subprocess can't refresh and emits the binary's "Please run /login" text as assistant content. Verified by inspecting the binary's string table. Provider now intercepts and routes a structurederrorCode: 'auth_failed'to the coordinator; coordinator suppresses the leak and self-heals via session release. Covers all four dispatch paths (solo, parallel, chatroom speaker, chatroom moderator). - codex CLI ↔ SDK protocol mismatch silent no-reply — a globally-installed codex CLI at a version different from the bundled SDK silently emits events the SDK can't decode, every dispatch returns empty
assistantText, no reply ever reaches the user. The fix infind-codex-binaryfrom v0.5.6 only worked for plugin installs; the desktop installer still hit this. Boot now comparescodex --versionagainst the bundled@openai/codexversion and refuses to register the provider on mismatch — loud[BOOT]log instead of silent failure. SessionManager.sweepIdle()was dead config — the method existed but was never called from production paths, soidleEvictMs: 30 * 60_000did nothing. Wired to a 60 ssetInterval. Long turns are protected by an in-flight counter so a mid-stream dispatch isn't killed by the timer.- Recovery affordance —
/reset(/重置) drops every registered provider's session for the current chat; next message starts fresh./health aishows per-provider session age.wechat-cc doctornow reports the installedclaude --versionandcodex --version.
What changed
1. claude auth-fail sentinel interception
-
7a52114fix(provider): stop leaking claude "Not logged in" as chat reply + wire idle sweep(PR #32 first commit)
Provider intercepts assistant text matchingPlease run \/loginorNot logged inand emits{ kind: 'error', code: 'auth_failed', message }instead. Coordinator'sdispatchSolobranches onsummary.errorCode === 'auth_failed'to suppress the fallback-reply path and send one neutral notice (AI 暂时不可用,请在 wechat-cc 桌面端检查并重新连接) throttled to ≤ 1/hour/chat. SessionManager.sweepIdle() finally wired to a 60s setInterval (the method existed but was never called from production paths)..unref()so the timer never holds the event loop. -
65660acfix(p1.5): four review findings — self-heal, in-flight safety, sentinel coverage, codex version gate
Round-1 review surfaced four real issues. Coordinator now releases the session onauth_failedso busy chats self-heal automatically (without this, a chat that keeps bumpinglastUsedAtnever goes idle and stays broken for the full window). SessionManager.sweepIdle skips sessions with an in-flight dispatch (long turns no longer killed mid-stream).AUTH_FAIL_REbroadened to catch the split-chunk case where the SDK emits "Not logged in" and "/login" as separate assistant events. Bootstrap refuses to register codex whencodex --version≠ bundled@openai/codexversion. -
2793404fix(p1.6): two round-2 review findings — semver prerelease + close-propagates contract
Round-2 review caught the SEMVER regex silently mangling0.128.0-rc.1to0.128.0. Widened to capture optional-prerelease. Added a regression test pinning the close()-propagates-to-iterator contract that the in-flight counter relies on.
2. /both, /chat, and moderator coverage
94c7bcbfix: plug auth_failed in /both + /chat paths, harden moderator, Tauri bundle probe(PR #33)
Post-merge audit of PR #32 found onlydispatchSolowas readingerrorCode./both(parallel) and/chat(chatroom) had the same gap — provider correctly intercepted the leakage but coordinator didn't release the failing session, so a stale-credential provider stayed cached for 30 min with every dispatch silently producing no reply from that side. The chatroom moderator's own haiku-4-5 call shared the same credential chain and silently degraded to alternation when stale. All four paths (solo, parallel, chatroom speaker, chatroom moderator) now intercept consistently.find-codex-binaryderives the macOS.app/Contents/Resources/path fromprocess.execPathand probes a bundled codex shim first when available — code-level wiring ready for the build-system follow-up that ships@openai/codexinside the Tauri bundle.
3. Admin recovery commands
e9698bcfeat(admin/doctor): /reset + /health ai in-chat, plus CLI version probe(PR #32 second commit)
In-chat operator affordances./reset(and the Chinese alias/重置) iterates the registry and callssessionManager.release(alias, provider)+sessionStore.delete(alias)so the next dispatch starts from clean state./health ailists per-provider session age — pure bookkeeping inspection, no token.wechat-cc doctoradds aprobeBinaryVersionhelper that runsclaude --versionandcodex --versionvia spawnSync (3s cap) and surfaces both in the report — gives support flows a one-glance signal for the SDK↔CLI mismatch trap.
4. Acceptance script
2ced13e+213f2dctest(acceptance): end-to-end harness for the four manual-acceptance checks+ typecheck fix
Self-contained smoke atscripts/acceptance-p0p1.ts. Boots an isolated daemon (does not touch production state), exercises each user-facing path: auth_failed sentinel never leaks; coordinator releases session + sends throttled notice;/reset//health aioperate against real bootstrap-wired SessionManager; codex version mismatch refusal at boot. PR #33 extended to seven scenarios covering /both, /chat speaker, and chatroom moderator. Not wired into CI — operator-grade smoke for re-verification when refactoring this area.
How to apply
After updating to v0.5.17:
- You don't need to do anything for the common case. The daemon now auto-recycles a stale session on the next message; most users won't see any user-visible signal of the underlying recovery.
- If you see
⚠ AI 暂时不可用...— most likely transient; the daemon is mid-recycle. Send/resetif it doesn't resolve on retry, or checkwechat-cc doctorfor the underlying status. - If
wechat-cc doctorshowscodexat a version different from the bundled SDK — the BOOT log will refuse to register codex. Runnpm i -g @openai/codex@<expected-version>(the boot log includes the exact version), or remove the older codex from PATH. Restart the daemon.
Behind the scenes
A look at the iteration depth: this release went through four review rounds (P0 → P1.5 → P1.6 → P1.7) before reaching merge, with one Claude code-reviewer and one Codex independent reviewer agreeing on findings at each step. The cycle surfaced:
- Round 1 (P0 → P1.5): four issues the P0 fix didn't cover — most critically, the coordinator wasn't releasing the session on
auth_failed, so a busy chat could never self-heal (lastUsedAtkeeps bumping → sweepIdle never fires). - Round 2 (P1.5 → P1.6): SEMVER regex silently mangling prerelease versions, plus a contract-level concern about the in-flight counter that turned out to hold in practice for our providers (regression test pins the contract for future provider rewrites).
- Round 3 (P1.6 → P1.7): only the
solodispatch path was covered —/both,/chat, and the moderator's own query had the same structural gap.
The "cc-connect reset_on_idle_mins" pattern was the design north star — explicitly chose idle-reset + reactive interception over spawn-per-dispatch after surveying how chenhg5/cc-connect and similar projects on GitHub handle the same class of problem. Keeps warm-start performance; reactive interception + auto-release covers the busy-chat edge case.
What's still pending
- Desktop dashboard "AI 状态" tile + Reset / Sign-in buttons — separate UI-focused PR. The internal-api routes will land alongside the Vue rendering.
- Tauri build-config to ship
@openai/codexin Resources — the find-codex probe is ready; needs a per-platform bundle entry intauri.conf.json. - WarmQuery pool — only if measured cold-start ever shows up in real usage.
- haiku-eval empty-response handling — not auth-related; rate-limit/network-empty case currently silently degrades to alternation. Reviewer flagged as a separate class of failure to address later.
Tests
- 17 new unit tests across the release line, all TDD'd (RED before GREEN, except one tests-after regression-pinning test in P1.6 which is flagged in its commit message).
- 1654/1655 vitest pass (the 1 send-reply preflight network timeout fails on master too — pre-existing flake, unrelated).
- typecheck clean.
- 9/9 playwright e2e green (apps/desktop/playwright).
- 23/23 acceptance harness assertions pass against the host's live
codex 0.125 ↔ SDK 0.128mismatch.