fix(review): four findings from adversarial Codex review round 1

- twitter: align backend selection with the two-phase findings pattern —
  an unauthenticated twitter-cli (warn) no longer blocks a fully-working
  OpenCLI (ok) further down the candidate list
- watch: use _is_newer_version like check-update (the != comparison kept
  the downgrade prompt this branch fixed elsewhere)
- uninstall: third copy of the skill-dir removal also gets the symlink
  guard (full `agent-reach uninstall` path)
- doctor: a stale active_backend from a previous check on the singleton
  channel no longer leaks into an errored result

+4 regression tests (162 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Pnant
2026-06-11 17:53:37 +08:00
parent fc24fb699b
commit cc90e10b19
6 changed files with 100 additions and 16 deletions
+17 -12
View File
@@ -17,9 +17,14 @@ class TwitterChannel(Channel):
return "x.com" in d or "twitter.com" in d
def check(self, config=None):
"""按 backends 顺序真实探测,第一个活着的后端即为 active_backend。"""
"""Probe candidates in order; first fully-usable backend wins.
与其他多后端渠道同一套两段式:先收集全部候选状态,第一个 ok 获胜;
没有 ok 才轮到第一个 warn——否则「装了但未登录」的 twitter-cli
会把排在后面、完整可用的 OpenCLI 挡在门外。
"""
self.active_backend = None
failures = []
findings = []
for backend in self.ordered_backends(config):
if backend == "twitter-cli":
@@ -32,18 +37,18 @@ class TwitterChannel(Channel):
continue
if result is None:
continue # 未安装——继续尝试下一个后端
continue # 未安装——不参与候选
findings.append((backend, *result))
status, message = result
if status in ("ok", "warn"):
# 工具本身是活的(含已装但未登录的 warn)
self.active_backend = backend
return status, message
# broken/timeout —— 记下处方,继续尝试下一个后端
failures.append(message)
for wanted in ("ok", "warn"):
for backend, status, message in findings:
if status == wanted:
self.active_backend = backend
return status, message
if findings: # 只剩 broken/timeout 候选
return "error", "\n".join(m for _, _, m in findings)
if failures:
return "error", "\n".join(failures)
return "warn", (
"Twitter CLI 未安装。安装方式:\n"
" pipx install twitter-cli\n"
+5 -2
View File
@@ -1387,7 +1387,10 @@ def _cmd_uninstall(args):
print(f"[dry-run] Would remove {platform_name} skill: {skill_path}")
else:
try:
shutil.rmtree(skill_path)
if os.path.islink(skill_path):
os.unlink(skill_path)
else:
shutil.rmtree(skill_path)
print(f" Removed {platform_name} skill: {skill_path}")
removed_any = True
except Exception as e:
@@ -1762,7 +1765,7 @@ def _cmd_watch():
if not err and resp and resp.status_code == 200:
data = resp.json()
latest = data.get("tag_name", "").lstrip("v")
if latest and latest != __version__:
if latest and _is_newer_version(latest, __version__):
update_available = True
new_version = latest
release_body = data.get("body", "")
+5 -2
View File
@@ -19,15 +19,18 @@ def check_all(config: Config) -> Dict[str, dict]:
for ch in get_all_channels():
try:
status, message = ch.check(config)
active = getattr(ch, "active_backend", None)
except Exception as e: # noqa: BLE001 — doctor must survive any channel
status, message = "error", f"体检异常:{e}"
# Channels are registry singletons: a stale active_backend from a
# previous check must not leak into an errored result.
status, message, active = "error", f"体检异常:{e}", None
results[ch.name] = {
"status": status,
"name": ch.description,
"message": message,
"tier": ch.tier,
"backends": ch.backends,
"active_backend": getattr(ch, "active_backend", None),
"active_backend": active,
}
return results