Skip to content

fix(desktop): GitHub 增强搜不到时回退 gh CLI,空列表不再谎称从未提交 - #1224

Merged
MagicLizi merged 7 commits into
mainfrom
dash/issues-enhancement-fallback
Jul 31, 2026
Merged

fix(desktop): GitHub 增强搜不到时回退 gh CLI,空列表不再谎称从未提交#1224
MagicLizi merged 7 commits into
mainfrom
dash/issues-enhancement-fallback

Conversation

@dashhuang

Copy link
Copy Markdown
Member

这次改了什么

摘要

用户报告:本机 /issues 一条 issue 都看不到,而他 GitHub 名下在本仓有 34 条,页面却显示「还没有提交过 Issue」。

逐层定位到三个叠加缺陷(都已实测确证,非推测):

1. 通道在身份阶段一锤定音,之后不回退。 resolveGithubEnhancement 里插件一旦报出身份就 return { source: 'ghost' },本机 gh CLI 从此永不被尝试 —— 这把「身份可用」当成了「这一路能查到数据」,而两者无关。

实测该 PAT 是 fine-grained token:get_current_user 正常(所以 header 正常显示 @dashhuang),搜本仓却被 GitHub 以 422 拒绝:

The listed users and repositories cannot be searched either because
the resources do not exist or you do not have permission to view them.

仓库是 PUBLIC(gh repo view 确认)—— fine-grained token 的 search API 对未显式授权的仓库一律拒绝,即使仓库公开。而本机 gh 的 OAuth token 能搜到全部 34 条,却因为插件「成功」了而根本没被调用。

2. 失败完全静默。 搜索失败只 log.debug,默认不落盘。排查时日志里只有平台通道的 404,这条路的失败线上完全不可诊断。

3. 空态在撒谎。 三路都没数据时 UI 断言「还没有提交过 Issue」。这与 #1103platform-unavailable 修过的是同一类(「空的本机兜底不能证明远端历史为空」)—— 平台那两路修了,增强这一路漏了,这次是用户先撞上的。

即使去给 PAT 补仓库授权能解决单个用户的问题,产品也必须兜住:PAT 的权限范围不可控,而失败静默时用户根本不会知道要去改它。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护

范围

  • 关联 Issue / 需求:用户实机反馈(本仓 /issues 页面看不到自己的 issue);延续 feat(desktop): Issue 页展示自己提交过的 Issue #1103
  • 本 PR 包含:
    • service 层新增可选依赖 searchAuthoredIssuesFallback,主通道搜索失败时换本机 gh CLI 再试;只对 ghost 主通道回退(gh-cli 自己就是兜底,没有下一条可换)
    • 搜索失败日志 debugwarn,并新增 githubEnhancementFailed 跨进程回传
    • 空态标题判据收口到 canTrustEmptyList(),新增 emptyTitleUnverified
    • 增强失败时的提示 enhancementFailedHint(含插件令牌权限的可操作指引)
    • 四语补齐,GHOST_SEARCH_TIMEOUT_MS 6s → 4s 给兜底留预算
  • 明确不包含:
    • 服务端 GET /api/github/issues/mine 仍未提供(platform-unavailable 降级不变,接口上线后零改动生效)
    • 「配了插件但身份解析就失败」(PAT 完全失效)仍走静默 —— 区分它需要改 resolveGithubEnhancement 的返回契约,且那种情况下空态标题已因 platform-unavailable 显示为准确的「暂时查不到」,优先级低;日志提到 warn 后至少线上可诊断
  • 用户可见变化:
    • 插件 PAT 搜不动本仓时,列表照常出内容(改前是空的)
    • 兜底也不可用时新增一条提示,并指出可能是插件令牌权限不足
    • 列表为空且无法确证时,标题从「还没有提交过 Issue」改为「暂时查不到你的 Issue」
  • 是否存在 breaking change:无

UI 变化

深色模式实机截图(macOS,35 条数据下的列表态):见下方「手工验证」的取证。本次新增/改动的可见元素:降级提示条多一条(enhancementFailedHint)、空态标题分两版。

  • 引用的设计规范:
    • DESIGN.md §2 灰阶:新增提示沿用既有 notices 容器(bg-sidebar-item-hover + text-sidebar-muted),未引入任何语义色
    • DESIGN.md §10 Token Selection Rules for New UI:无新增 token,复用既有 slot;未使用 bg-[#xxx] dark:bg-[#xxx] 形态
    • DESIGN.md §11 Voice & Content:提示不断言页面上不存在的数据范围;空态标题只在能确证时才说「还没有提交过」;措辞不暗示「必须有 GitHub 账号」(只针对已配置插件的用户说令牌权限)
    • i18n/GLOSSARY.md:Issue 保留英文(2026-07 裁决);pnpm check:i18n-glossary 通过
    • 圆角未新增(沿用容器既有 rounded-xl),无 4px / 6px 残留

怎么验证的

自动验证

bash Skills/git/scripts/run-unit-gate.sh
结果:GATE_EXIT=0(56 个 workspace PASS / 0 FAIL)

pnpm --filter desktop run --if-present typecheck
结果:通过

pnpm check:i18n-glossary
结果:✅ en / zh-CN / ja / ko 无新增违规

相关用例:143 例全绿(github-issue 全部 + issue-tracker 全部 + myIssuesIpc)

新增 7 条 service 用例覆盖回退不变量:主通道失败 → 调兜底并采用其结果且不算失败/兜底返回 null → 标记失败但主列表照常/兜底自己抛错 → 同上不打挂/gh-cli 主通道失败时不调兜底/主通道成功时不碰兜底/没配增强既不搜也不算失败/兜底也算在同一次总 deadline 内。

变异验证(回退与空态判据都是「静默失败」型缺陷,没有能鉴别的用例等于没修):去掉回退调用、把空态标题固定回 emptyTitle 后,对应 4 条用例失败、其余不受影响;恢复后全绿。

手工验证

--preserve-running passive dev 实例(macOS,复用真实登录态与插件 PAT),经 CDP 对 /issues 发起 force: true 强制刷新:

{"success":true,"count":35,"degraded":"platform-unavailable",
 "enhancement":{"login":"dashhuang","source":"ghost"},
 "enhancementFailed":false,"truncated":false,
 "firstThree":[{"n":1222,"state":"open","sources":["github-account"]},
               {"n":1082,"state":"closed","sources":["github-account"]},
               {"n":1016,"state":"open","sources":["github-account"]}]}

count: 35 = GitHub 名下 34 条 + 账本里 1 条平台代发(改前是 0)。

main 日志的完整取证链(两次调用一致,证明走的是回退而非「插件突然好了」):

20:54:01 [WARN] serverApiClient  /api/github/issues/mine → 404          ← 平台仍未上线
20:54:02 [WARN] my-issues  github enhancement search failed; trying the fallback channel
                           { source: 'ghost', error: 'HTTP 422 ...permission to view them...' }
20:54:03 [INFO] my-issues  github enhancement recovered through the fallback channel { count: 35 }

这条 warn 也顺带验证了缺陷 2 的修复:改前是 debug、日志里什么都看不到。原始 422 文本只进 main 日志、不跨进程到 renderer(不含 token 或本地路径)。

深色模式目检通过:列表行版式、状态点(实心 open / 空心 closed)、来源标记、#编号 · 状态 · 类型 · 来源 · 日期 · 评论数 元信息行、顶部常驻说明条,以及 platformUnavailablePartialHint(混合列表下正确地没有谎称「只显示本机记录」)。回退成功时未出现 enhancementFailedHint ✓。

未执行的验证

  • 浅色模式未实机目检。 改动全部走语义 token,但按 DESIGN.md §10 这不等于双模式已验证。原因:应用主题由 React 按设置状态注入 token 值,CDP 的 Emulation.setEmulatedMedia 只能改 data-theme 标记、颜色不跟着变;要真正切换必须改显示模式设置,而 --preserve-running 与用户主实例共享 userData,改它会影响用户正在使用的实例,因此没有动。未目检的具体元素:enhancementFailedHint 提示条与 emptyTitleUnverified 空态标题在浅色下的对比度。
  • 「兜底通道也不可用」分支未实机复现(需要临时破坏本机 gh 环境)。该分支由 3 条单测覆盖(返回 null / 自己抛错 / gh-cli 主通道不回退),变异验证有效。
  • 服务端接口就绪后的真实数据路径无法验证(接口还不存在)。

风险

风险分类

  • 无已知风险

影响与回滚

  • 影响范围:仅 /issues 页面的可选增强取数路径与空态文案。主通道(平台)与本机账本逻辑未动;提交 issue 链路未动。回退只在主通道已经失败时触发,不增加正常路径的请求量。
  • 回滚 / 降级方式:整个 PR 可直接 revert,/issues 回到「插件搜不动就整路放弃」的行为;不涉及数据迁移、持久化格式或协议变更。

🤖 Generated with Claude Code

用户实测:本机 /issues 一条都看不到,而他 GitHub 名下在本仓有 34 条,页面却说
「还没有提交过 Issue」。逐层定位到三个叠加缺陷:

1) 通道在身份阶段一锤定音。resolveGithubEnhancement 里插件一旦报出身份就
   `return { source: 'ghost' }`,gh CLI 从此永不被尝试 —— 把「身份可用」当成了
   「这一路能查到数据」。实测该 PAT 是 fine-grained token:get_current_user 正常,
   搜本仓被 GitHub 以 422 拒绝(未显式授权的仓库即使公开也搜不到),而本机 gh 的
   OAuth token 能搜到全部 34 条,却因为插件「成功」了而根本没被调用。
   修:service 层加 searchAuthoredIssuesFallback,主通道失败换本机 gh CLI 再试。
   放 service 而不是 runtime —— runtime 是真实接线、没有单测,这条不变量必须能被
   用例钉住。只对 ghost 主通道回退(gh-cli 自己就是兜底)。

2) 失败完全静默。搜索失败只 log.debug,默认不落盘;排查时日志里只有平台通道的 404,
   这条路的失败线上不可诊断。提到 log.warn,并新增 githubEnhancementFailed 跨进程
   回传,让 UI 有机会说明「你 GitHub 那部分也没进来」。回退成功时为 false ——
   没有可见损失就不打扰用户。

3) 空态在撒谎。三路都没数据时 UI 断言「还没有提交过 Issue」。与 #1103 为
   platform-unavailable 修过的是同一类(「空的本机兜底不能证明远端历史为空」),
   平台那两路修了、增强这一路漏了,这次是用户先撞上的。
   判据收口到 canTrustEmptyList():只有三路都查询成功才敢那么说,否则改用
   emptyTitleUnverified「暂时查不到你的 Issue」。

超时预算:GHOST_SEARCH_TIMEOUT_MS 6s → 4s,给兜底留空间;增强总 deadline 仍是 8s,
两段合计不超预算(页面不比现在慢)。四语新增 emptyTitleUnverified 与
enhancementFailedHint(后者按产品决定给出插件令牌权限的可操作指引)。

变异验证:去掉回退 / 固定空态标题时,对应 4 条用例失败、其余不受影响。
门禁 GATE_EXIT=0(56 workspace PASS / 0 FAIL)、desktop typecheck 通过、
check:i18n-glossary 通过、改动文件无 lint 问题。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 13:01
@dashhuang
dashhuang requested a review from a team as a code owner July 31, 2026 13:01
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

本次修改完善了桌面端“我的 Issue”查询与首屏体验。

  • GitHub 插件搜索失败时,在预算允许的情况下改用本机 gh CLI 重试。
  • 将增强通道的失败状态经 IPC 传到 renderer,并为未确证的空列表采用保守文案。
  • 新增按账号隔离、清洗并持久化的首屏快照,fresh 查询完成后再接管页面。
  • 补齐四语文案、术语表以及相关 service、IPC、快照和布局测试。

Confidence Score: 5/5

该 PR 看起来可以安全合并,现有剩余问题仅涉及极慢兜底请求在总 deadline 后继续消耗资源。

当前实现已经通过剩余预算检查避免大部分注定超时的兜底调用;不过,已启动的 gh CLI 搜索没有取消能力,请求超过剩余预算时仍会在后台完成,并可能与后续查询重叠。

Files Needing Attention: apps/desktop/src/main/github-issue/myIssuesService.ts

Important Files Changed

Filename Overview
apps/desktop/src/main/github-issue/myIssuesService.ts 引入通道健康状态、GitHub 搜索兜底、共享总预算和首屏快照写入判据。
apps/desktop/src/main/github-issue/myIssuesRuntime.ts 接入 gh CLI 搜索兜底、快照存储,并区分未配置与身份解析失败。
apps/desktop/src/main/github-issue/myIssuesSnapshotStore.ts 新增按账号路径隔离且对落盘数据进行严格清洗的首屏快照存储。
apps/desktop/src/renderer/features/issue-tracker/hooks/useMyIssues.ts 首屏先 hydrate 快照,并明确区分快照数据与本轮 fresh 查询结果。
apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts 收紧空列表可信判据,并根据增强通道状态选择提示。
apps/desktop/src/main/maker-ipc/my-issues.ts 新增受可信 renderer 来源保护的快照 IPC,并传递增强失败状态。
apps/desktop/src/shared/myIssues.ts 扩展查询结果与快照的跨进程共享契约。

Sequence Diagram

sequenceDiagram
  participant UI as /issues Renderer
  participant IPC as Electron IPC
  participant Service as MyIssuesService
  participant Ghost as GitHub 插件
  participant GH as 本机 gh CLI
  participant Snapshot as 账号级快照

  UI->>IPC: 读取首屏快照
  IPC->>Snapshot: read
  Snapshot-->>UI: 上次列表或 null
  UI->>IPC: 查询 fresh 列表
  IPC->>Service: list()
  Service->>Ghost: 解析身份并搜索
  alt 插件搜索成功
    Ghost-->>Service: Issue 列表
  else 插件搜索失败且预算充足
    Service->>GH: fallback 搜索
    GH-->>Service: Issue 列表或失败
  end
  Service->>Snapshot: 健康结果写入快照
  Service-->>UI: fresh 结果与通道状态
Loading

Reviews (5): Last reviewed commit: "refactor(desktop): 三路输入显式报健康,快照判据不再从残缺信息..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/github-issue/myIssuesService.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bea2dd85b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts Outdated
Comment thread apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 修复 Desktop /issues 页「GitHub 身份增强」在搜索失败时不会回退到本机 gh CLI、并导致空列表误导用户为“从未提交过 Issue”的问题;同时提升失败可诊断性(warn 日志 + 跨进程标记)并补齐相关 UI/i18n 与测试覆盖。

Changes:

  • MyIssuesService 中为 ghost 增强通道引入可选兜底 searchAuthoredIssuesFallback(本机 gh CLI),主通道搜索失败时尝试回退并复用同一次总 deadline。
  • 新增 githubEnhancementFailed 跨进程字段,并在 UI notices 与空态标题中区分“确证为空” vs “本次不可确证为空”,避免空态撒谎。
  • 增强通道搜索失败日志从 debug 提升为 warn,新增提示文案(四语)说明可能的 token 权限问题。

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/desktop/src/shared/myIssues.ts 为结果模型新增 githubEnhancementFailed,区分“没配增强”与“配了但没用上”。
apps/desktop/src/renderer/vite-env.d.ts 同步 IPC 返回类型的 githubEnhancementFailed 字段默认值。
apps/desktop/src/renderer/i18n/locales/zh-CN/common.json 新增空态标题 emptyTitleUnverified 与增强失败提示 enhancementFailedHint
apps/desktop/src/renderer/i18n/locales/ko/common.json 同步新增 emptyTitleUnverified / enhancementFailedHint 韩文文案。
apps/desktop/src/renderer/i18n/locales/ja/common.json 同步新增 emptyTitleUnverified / enhancementFailedHint 日文文案。
apps/desktop/src/renderer/i18n/locales/en/common.json 同步新增 emptyTitleUnverified / enhancementFailedHint 英文文案。
apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts 新增 canTrustEmptyList() 并在 notices 中加入增强失败提示 key。
apps/desktop/src/renderer/features/issue-tracker/IssueTrackerFeatureLayout.tsx 空态标题改为基于 canTrustEmptyList() 的两版文案,避免不可确证时断言“从未提交”。
apps/desktop/src/renderer/features/issue-tracker/hooks/useMyIssues.ts renderer 侧接入并保存 githubEnhancementFailed
apps/desktop/src/renderer/features/issue-tracker/tests/myIssuesNotices.test.ts 覆盖增强失败提示与 canTrustEmptyList() 判据。
apps/desktop/src/renderer/features/issue-tracker/tests/IssueTrackerFeatureLayout.test.tsx 覆盖首屏/空列表在不同可确证条件下的标题选择。
apps/desktop/src/main/maker-ipc/my-issues.ts IPC 响应结构补齐 githubEnhancementFailed 默认值。
apps/desktop/src/main/maker-ipc/tests/myIssuesIpc.test.ts IPC 测试同步断言 githubEnhancementFailed 字段。
apps/desktop/src/main/github-issue/myIssuesService.ts service 层实现增强搜索失败后的 fallback 尝试、warn 日志与 githubEnhancementFailed 回传。
apps/desktop/src/main/github-issue/myIssuesRuntime.ts runtime 层接线 fallback(gh auth token)并抽取共用查询参数;调整 ghost 搜索超时预算。
apps/desktop/src/main/github-issue/tests/myIssuesService.test.ts 新增用例覆盖“主通道失败→fallback”“fallback 不可用/失败”“不重复预算”等不变量。

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/desktop/src/main/github-issue/myIssuesService.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@dashhuang 👋 这个 PR 还有 7 条 review conversation 没 resolve(apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts / apps/desktop/src/main/github-issue/myIssuesService.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

用户实测:点开 /issues 先看到一个「没有 issue」的状态,几秒后才跳出 35 条。

两个成因叠加:
1) 首屏没有任何本地数据可显示。service 那层的 60s TTL 缓存是**内存**的,进程一重启即
   失效、冷启动必然 miss;而列表要等平台通道与 GitHub 增强都落地才出得来(增强走插件
   失败 + gh CLI 回退时实测约 2s)。
2) 等待期间显示的是一句错误断言。#1103 为遵守 §7「取数期间界面不发生变化」让首屏保留
   引导内容,但那个引导带着结论性标题 —— 有 35 条的用户先被告知「暂时查不到你的 Issue」
   再跳成列表。比 loading 文案更糟:它是错的,而且照样是两次形态切换。

本次:
- 新增 myIssuesSnapshotStore(electron-store + ownerScopedUserDataPath,形状照同目录的
  submittedIssueLedger)。**不照搬** device-link/mirrorCacheStore 那 1500 行:它的 purge
  队列、跨进程锁、作废屏障是为多设备消息文件与内联媒体设计的。但借用它的语义边界 ——
  快照是可重建的首屏镜像、不是真相,fresh 一到即整体接管。
- 快照**刻意不含** degraded / githubEnhancementFailed / truncated:那三个描述的是
  「这一次查得怎么样」,缓存它们会让用户进页面就看到一条过期的错误提示。
- 写入点放 settle()(结果落地的唯一收口,scope 校验已在那里),判据与内存缓存**同一个**:
  epoch 变了说明期间有提交成功过,落一份已知过时的镜像没有收益。不为快照另立判据。
- 读取走新 channel MY_ISSUES_SNAPSHOT(同一道 assertTrustedAppRendererEvent —— 快照含
  issue 标题与 GitHub 用户名,是账号私有数据)。不给 listMyIssues 加 cachedOnly 参数:
  那样得再引入字段区分「缓存是空列表」与「根本没有缓存」,把 list 的契约搞混。
- useMyIssues 里 fresh 与快照**分开存**,并新增 hasFreshData。快照的空列表**不构成**
  「查证过的空」—— 若空态标题只看 canTrustEmptyList,首屏又会冒出「还没有提交过 Issue」,
  就是这一族错误的第三次。hasFreshData 为 false 时整个标题不渲染(引导正文与 CTA 照旧
  在场,它们无论有没有 issue 都成立)。
- 快照读取与真实查询**并行发起**,不 await 它再查:占位不该挡在最慢那条路前面。

变异验证:trustable 不看 hasFreshData / settle 不写快照 / 快照 url 改信落盘值 ——
4 条用例各自失败、其余不受影响。门禁 GATE_EXIT=0(56 workspace PASS / 0 FAIL)、
desktop typecheck 通过、改动文件无 lint 问题;相关用例 166 例全绿。

文案零新增(沿用 #1224 的 emptyTitle / emptyTitleUnverified),四语无改动。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
菜单项回答的是「点这里能干什么」,而它的邻居全是动作短语(帮助 / 检查更新 /
最新更新介绍 / 新建对话)—— zh-CN 与 ja 里 issues 是**唯一**一个孤立的英文名词,
既断了风格,也让不熟悉 GitHub 的用户不知道该不该点。

zh-CN 用「问题反馈」、ja 用「フィードバック」(ja 同级项同样全是日文,Issue 一样孤立)。
en 保持 Issues、ko 保持 이슈 —— 两者在本语言里本就是自然的入口词。

**这不是给 Issue 起中文译名**,术语裁决不变:指代该类对象的位置(页面标题、正文、
提示)仍写 Issue,因为点进去就跳 GitHub,名字必须对得上。判据与 glossary 里 ja 的
Jira 豁免同源 —— 跟用户实际看到的外部界面保持一致。已在 glossary 的 issue 条目
note 里写明这条区分,免得后来人当成漏统一。

顺带修掉 MenuButton 注释里的「议题」—— 那是被 forbidden 的旧译名。

check:i18n-glossary 通过(0 新增违规;改 glossary.json 后按提示跑了
i18n:glossary-doc 重新生成人读版)。desktop typecheck 通过、
run-unit-gate.sh GATE_EXIT=0(56 workspace PASS / 0 FAIL)。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
处理 #1224 的 7 条 review 反馈,归族后是 4 个问题:

1) 「没配增强」被当成「查询完整」(greptile P1 + codex P2 + copilot,4 条同族)。
   canTrustEmptyList 原先只看 degraded === null && !githubEnhancementFailed,而没配
   增强时后者是 false(没配不是失败)—— 于是「GitHub 账号那一路从未查过」被当成
   「查过且为空」。平台侧不知道用户绕过 Cindy 直接在 GitHub 提的那些 issue,只有增强
   查得到,所以那种用户会看到「还没有提交过 Issue」,**正是本 PR 要修的错误断言**。
   判据补上 githubEnhancement !== null。代价是那句话只在配了增强的用户身上出现,
   没配的看到「暂时查不到」—— 刻意保守,同本页反复确立的「错误信息比保守措辞更糟」。
   注释也改准了(copilot 指出原注释写「三路都成功」与实现不符)。

2) 失败提示无条件指向插件页(codex P2 + auto-review P2)。githubEnhancementFailed 在
   source === 'gh-cli' 时同样为 true(searchViaFallback 对非 ghost 主通道直接判失败),
   而那种用户根本没在用插件 —— 给他「插件令牌权限不足、去插件页检查」等于指向不存在
   的页面,排障方向也错(gh 用完整 OAuth token,失败多为网络或额度)。新增
   enhancementFailedGenericHint(四语齐),按 githubEnhancement.source 选。

3) 兜底请求在 deadline 后仍在跑(greptile P2)。withDeadline 只停止等待,而
   GithubClient 不支持 AbortSignal(加上会动到 git-context 等其它调用方,超出本 PR)。
   改为**剩余预算不足就不启动**:低于 MIN_FALLBACK_BUDGET_MS(1.5s)直接判失败,
   不发那次注定被丢弃、又照样耗 GitHub 额度的请求。判据收口在 canTryFallback(),
   searchViaFallback 里重复的那半条判断一并删掉 —— 两处判据迟早分歧。
   (greptile 说的「连续刷新会累积」不成立:list() 有 in-flight 去重,force 也复用在途。)

4) 日志文案误导排障(copilot)。warn 固定写 "trying the fallback channel",但 gh-cli
   主通道或没注入 fallback 时并不会真去试。现按 canTryFallback() 的结果分两种文案。

变异验证:三处实现改动(去掉 githubEnhancement 判据 / 提示不分来源 / 不做预算检查)
各自退回后,4 条用例失败、其余不受影响。门禁 GATE_EXIT=0、desktop typecheck 通过、
check:i18n-glossary 通过;相关用例 172 例全绿。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 15:07
@dashhuang

Copy link
Copy Markdown
Member Author

范围更新:两个 PR 已合成这一个

按 Dash 的要求,把原 #1229(首屏落盘快照 + 菜单入口文案)合并进本 PR —— 那是个 base 指向本分支的 stacked PR,它的 CodeQL 全套不触发(6 项 check vs 本 PR 的 12 项),等于安全扫描完全没覆盖。合并后消掉了这个代价,也不用再按顺序合。

本 PR 现在 4 个 commit,同一个目标(让 /issues 真的可用):

commit 内容
bea2dd85 GitHub 增强搜不到时回退 gh CLI,空列表不再谎称从未提交
a50501ea 首屏读落盘快照(冷启动 1ms 出 35 条 vs 远端 2565ms),加载中不下结论
5f16c794 菜单入口用「问题反馈」,不再暴露 Issue 术语
a8c9d1cc 本轮 7 条 review 反馈的修复

本轮 7 条反馈已全部处理并 resolve

归族后是 4 个问题,逐条回在对应 thread 上。其中一条值得单独说:

canTrustEmptyList 把「没配增强」当成了「查询完整」(greptile P1 + codex P2 + copilot,4 条同族)。没配增强时 githubEnhancementFailed 是 false(没配不是失败),于是「GitHub 那一路从未查过」被当成「查过且为空」—— 而平台侧不知道用户绕过 Cindy 直接在 GitHub 提的那些,只有增强查得到。

这个 PR 前后修了三次同类错误(空账本不能证明远端为空 / 混合列表不能说只显示本机 / 快照的空不算查证过),第四次又在新加的判据里犯了。这一族的规律很清楚:只要新增一个「能不能确证」的判断,就要先问「这条数据源到底查没查过」,而不是「它有没有报错」。

另外两条修法与 reviewer 建议不同,已在 thread 里说明理由:

  • 兜底请求取消:GithubClient 不支持 AbortSignal,加它要动 git-context 等调用方 → 改成「剩余预算不足就不启动」。顺带更正一处:greptile 说的「连续刷新会累积」不成立,list() 有 in-flight 去重
  • 失败提示:按 githubEnhancement.source 分两版,gh-cli 用户不再被指向不存在的插件页

新增文案

enhancementFailedGenericHint(gh-cli 版失败提示)四语齐。check:i18n-glossary 通过。

验证

run-unit-gate.sh          GATE_EXIT=0(56 workspace PASS / 0 FAIL)
desktop typecheck         通过
check:i18n-glossary       通过(0 新增违规)
相关用例                   172 例全绿

变异验证:本轮三处实现改动各自退回后 4 条用例失败、其余不受影响。

实机(macOS,--preserve-running dev 实例,CDP 取证):

  • 回退生效,35 条出现(改前 0),日志三段链完整
  • 冷启动读快照 1ms / 35 条,远端 2565ms / 35 条;导航后 350ms 抓帧已是完整列表、无「没有 issue」中间态
  • title bar 菜单实测 ["帮助", "问题反馈", "检查更新"]

未做:浅色模式目检(主题由 React 按设置注入 token,CDP 改不动;真切换要动用户的显示模式设置,而 dev 实例与正式版共享 userData)、macOS 系统菜单目检(要抢前台)、切号瞬间首屏(需两个真实账号,由 4 条 scope 用例覆盖)。

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8c9d1cc02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/github-issue/myIssuesService.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@dashhuang 👋 这个 PR 还有 1 条 review conversation 没 resolve(apps/desktop/src/main/github-issue/myIssuesService.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

同一条不变量的第 6、7 处缺口 —— 这一页的任何断言都只能声称**这一次真查证过**的
范围,「没查过 / 查失败 / 只查到一部分」都不得被当成「查过且结果如此」。

1. 降级结果覆盖了完整快照(codex P2)

   settle() 无条件写快照。一次离线刷新会把 35 条的完整快照覆盖成账本子集甚至空列表,
   而快照刻意不带健康状况 —— 用户冷启动看到缩水列表加零提示;若仍离线,那份完整列表
   就永久没了。

   判据收在 isSnapshotWorthy():问「这一次有没有丢内容」,**不是**「有没有降级」。
   两者不等价,混起来会直接废掉整个功能 —— platform-unavailable(读接口还没上线)是
   当前所有用户的常态,把它算作不配写,快照永远写不出来。fetch-failed / not-signed-in
   是平台本该有却没拿到,才真的丢了内容。

   与 renderer 侧 canTrustEmptyList 刻意不合并:那边问「能不能断言从未提交」,空列表 +
   platform-unavailable 必须答否;这边问「这些内容能不能留给下次首屏」,同样组合答是。
   一个管断言缺失,一个管展示已有,方向相反。注释已写明,免得后人来合。

2. 同一个洞的第二个入口:身份解析失败被咽成「没配」

   runtime 把 gh 身份查询的异常 catch 成 null,于是与「没配」不可区分,service 判
   failed: false。token 过期 / 被撤销 / GitHub 限流时,用户直接在 GitHub 提的那些 issue
   静静消失,页面一个字都不说,缩水的结果还照样覆盖快照。

   改成:返回 null 只表示没配(静默),失败一律抛出 = 配了却问不出身份(算 failed)。
   插件那一步刻意不按此口径抛 —— 插件装了但从未授权是最常见情形,那属于「没配」,
   对它抛错会让所有装了插件的用户都看到失败提示。

3. 由此新可达的组合暴露出第 4 次同类错误(在我自己上一轮的修复里)

   githubEnhancement === null && failed === true 以前不可达,现在会真的出现。而提示
   判据写的是 `=== 'gh-cli' ? 通用 : 插件专属` —— default 分支又断言了自己不知道的事,
   把「连来源都不知道」误判成插件故障,指向一个那种用户根本没有的插件页。

   判据翻成「是不是 ghost」,未知一律落在保守的通用版。

验证:5 条变异各自被对应用例挡下(判据收紧成 degraded===null / 去掉增强那半条 /
去掉 worthiness 检查 / 解析抛错不算 failed / 提示判据反着写);仓库根 pnpm test:unit
GATE_EXIT=0(56 PASS / 0 FAIL);desktop typecheck 与 eslint 通过。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 15:41
@dashhuang

dashhuang commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

收敛检查点:一句话不变量 + 全部对称路径

这个 PR 已经在同一条不变量上栽了 10 次,其中 3 次是在修前一次时新造的。逐条打补丁不收敛,所以已按收敛止损规则改结构(见文末「已状态机化」),并把模型写在这里当后续 reviewer(含 bot)的锚点 —— 对着模型审比对着 diff 猜便宜得多。

不变量

这一页的每一条断言 —— 列表内容、空态标题、提示文案、落盘快照 —— 都只能声称这一次真的查证过的范围。「没查过」「查失败」「只查到一部分」都不得被当成「查过且结果如此」。

三路输入(平台通道 / 本机账本 / GitHub 增强)各自可能缺席,且缺席的理由不同:没配是正常状态,配了却用不上是内容真的少了,接口还没上线是平台侧压根没这份数据。这三者绝不能压成一个布尔。

对称路径清单(每处判据 + 未知时落在哪边)

# 判据 位置 问的问题 未知时落在
1 hasRemoteData notices 列表里有没有远端内容 state,不看 sources
2 platformNoticeKey notices 能不能说「只显示本机记录」 混合列表用不带该结论那版
3 canTrustEmptyList notices 能不能断言「从未提交」 不能 → 说「暂时查不到」
4 hasFreshData hook / layout 这一轮查回来了吗 没回来 → 整个标题不渲染
5 失败提示分版 notices 该指向哪条通道排障 通用版,不提插件
6 isSnapshotWorthy service 配不配留给下次首屏 不写,保留上一份
7 enhancement 状态 service 「配了却用不上」判全了吗 解析报错 / 超时都算
8 身份解析 null vs throw runtime 「没配」还是「配了问不出」 有通道配过 ⇒ 算失败
9 ledger 状态 service 账本读失败被记录了吗 failed,不静默
10 resolution.at 四态 service 身份停在哪一步 还在飞 ⇒ unknown

两个刻意不合并的判据

canTrustEmptyList#3)与 isSnapshotWorthy#6)在平台 absent(即 platform-unavailable)上故意给出相反答案,代码注释里也写了别去合:

一个管断言缺失,一个管展示已有。#6 收紧成「只认 ok」会当场废掉整个首屏快照 —— 接口未上线是当前所有用户的常态。4 个用例钉住这个方向。

10 次里有 5 次是同一个形状

default / else / 「拿不到就当没有」的分支断言了自己并不知道的事:

后续再加任何「能不能确证」的判断,要问的是「这条数据源到底查没查过」,而不是「它有没有报错」。

#8 另外给出一条教训

那处原本写了「刻意的例外」,理由是「插件装了但从未授权的用户会被误报」。前提是错的 —— isCindyGithubGhostUsableisGithubCredentialSaved(),那种用户根本进不到该分支。我写出了前提(所以 reviewer 能顺着它找到漏洞),但没去读被引用函数的定义核对。

刻意的例外必须写出前提,并且去验证那个前提。只写不验,等于把错误结论包装成经过思考的决定。

已状态机化(第 9、10 次之后不再补特例)

前 8 次都是「补一个特例」。第 9、10 次(账本读失败 / 身份解析还在飞就超时)暴露出根因是判据的输入本身残缺isSnapshotWorthyMyIssuesResult,而平台的健康挤在 degraded、增强的挤在 githubEnhancementFailed账本的根本没有位置。信息不在输入里,补多少次都会漏第 11 次。

现在三路显式报状态:

type ChannelState = 'ok' | 'absent' | 'failed' | 'unknown';
interface ChannelHealth { platform: ChannelState; ledger: ChannelState; enhancement: ChannelState }

四态而非布尔,是因为两个消费者对同一状态的处置方向相反

判据 unknown 落在
写不写快照 三路全 ∈ {ok, absent} 拒写(覆盖错了是永久数据丢失)
出不出提示 enhancement === 'failed' 静默(不断言我们不知道的事)

以前只有一个布尔,两个需求必然打架 —— 这正是「身份超时」修不干净的原因。加第四路输入时,ChannelHealth 会强迫调用方声明它的健康:漏输入从「靠人记得」变成结构上不可能。

测试口径

每处判据都有用例钉住两个方向(该说 / 不该说),并做变异验证:把判据改回错误写法,对应用例必须失败。累计验了 9 条,含「照 reviewer 建议直接实现 #6」那个改法,以及「把 absent 也当成丢内容」(会一次红 4 个用例)。

已知覆盖缺口myIssuesRuntime.ts 不进单测(依赖 Electron),所以 #8「什么时候抛」本身没有单测 —— 本 PR 有 3 个 bug 藏在这个文件里。被测试钉住的是它的后果链(解析抛错 ⇒ failed ⇒ 不写快照 ⇒ 通用提示)。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd5ed78b4d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/github-issue/myIssuesService.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (1)

apps/desktop/src/preload/preload.ts:4815

  • preload 侧 electronAPI.maker.listMyIssues 的错误返回类型少了 githubEnhancementFailed 字段,但 main 的 IPC 适配层与 renderer 侧类型声明都已把它作为稳定字段(错误分支固定为 false)。这会导致 preload 内的 API 声明与真实契约不一致,后续若在 preload/共享类型里复用该签名会产生类型漂移。
    // /issues 首屏快照(上次结果的落盘镜像);没有 / 坏掉返回 null。非权威,fresh 一到即接管。
    getMyIssuesSnapshot: (): Promise<import('../shared/myIssues').MyIssuesSnapshot | null> =>
      ipcRenderer.invoke('maker:issues:snapshot-mine'),
    // /issues 页面的「我的 Issue」列表;force=true 绕过 main 侧 60s TTL(手动刷新)。
    listMyIssues: (

codex 指出的第二处入口,而我上一轮为那处写的「刻意例外」建立在一个**错误前提**上。

上一轮只让 gh CLI 那条通道的身份失败抛出,插件那条仍静默落到「没配」,理由写的是
「isCindyGithubGhostUsable 为真只说明插件装了且启用,问不出 login 最常见的原因是用户
从未授权」。但 isCindyGithubGhostUsable 含 isGithubCredentialSaved() —— 装了插件却
从未授权的用户**压根进不到那个分支**。前提判错,「保守静默」的结论也就跟着错:凭据过期
被撤销、通道超时、GitHub 限流,全被当成「没配」,于是既不提示,缩水的结果还照样覆盖了
包含 GitHub 直提 issue 的完整快照。

判据改成「**有没有通道配过**」而不是「哪一步报了错」:任一通道配过却一个身份都没拿到
就抛出 ⇒ service 判 failed ⇒ 不覆盖快照 + 出提示。插件问不出身份时仍先落到 gh CLI 再试
一次,恢复路径不变。

两处如实登记的局限:
- 提示保持通用版。走到这里连来源都不知道,而未知一律落在保守那版(同本 PR 反复确立的
  口径)。代价是插件凭据过期的用户看到「稍后可以点右上角重试」——不算误导,但帮助有限;
  要给准确指引就得把「哪些通道配过」并进跨进程契约,那超出本 PR。
- runtime 不进单测,所以「什么时候抛」这个判断本身没有单测覆盖(本 PR 第 3 次被这一点
  藏住 bug)。被测试钉住的是后果链:解析抛错 ⇒ failed ⇒ 不写快照 ⇒ 通用提示。

验证:仓库根 pnpm test:unit GATE_EXIT=0(56 PASS / 0 FAIL);desktop typecheck 与 eslint
通过;issue-tracker 相关 179 例全绿。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 15:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ded58439f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/github-issue/myIssuesService.ts Outdated
Comment thread apps/desktop/src/main/github-issue/myIssuesService.ts Outdated
@MagicLizi

Copy link
Copy Markdown
Contributor

@dashhuang 👋 这个 PR 还有 2 条 review conversation 没 resolve(apps/desktop/src/main/github-issue/myIssuesService.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

同一条不变量连续第三轮被指出漏输入(先 degraded、再身份解析失败、现在账本失败与身份
超时)。前两轮都是补特例,这轮改结构 —— 按收敛规则,第 N 轮还在同一处打补丁就该停下来
状态机化,而不是再补第三个特例。

根因:isSnapshotWorthy 的输入是 MyIssuesResult,而那份结果里的健康信息**是残缺的** ——
平台的挤在 degraded、增强的挤在 githubEnhancementFailed、**账本的根本没有位置**
(读失败被静默换成空数组)。信息不在输入里,判据就必然漏。

改成三路各自显式报 ChannelState:
  ok      查了拿到了(空也是真的空)
  absent  没配 / 那边压根没这份数据 —— 正常状态,不是损失
  failed  本该有却没拿到 —— 内容真少了
  unknown 连配没配都没问出来(整体超时打断在半路)

两个消费者从同一份 health 各自推导,**方向刻意相反**:
- isSnapshotWorthy 要 ok|absent 全绿,failed 与 unknown 都拒写 —— 快照跨进程活到下次
  冷启动又不带健康状况,覆盖错了就是永久数据丢失,不确定必须保守拒写;
- githubEnhancementFailed 只认 failed,unknown 静默 —— 对没配增强的用户说「增强没用上」
  是在断言我们不知道的事。

以前只有一个布尔,这两个需求必然打架;分开后「身份解析超时」终于能同时做到不覆盖快照
且不谎报失败。

本轮 codex 指出的两条由此闭环:
1. 账本读失败仍算「配写」→ 丢了全部本机记录的结果覆盖完整快照。现在 ledger 报 failed。
2. 身份解析还在飞时整体超时 → promise 未 reject,旧实现只标记「已 reject」,于是当成
   「没配」。现在按解析停在哪一步分四态,pending ⇒ unknown。

absent 必须放行是这次最容易被"顺手收紧"掉的一条:platform-unavailable 是当前所有用户的
常态,当成丢内容的话首屏快照永远写不出来。已有用例钉住这个方向。

验证:4 条变异各自被对应用例挡下(账本失败仍报 ok / pending 当成 absent /
isSnapshotWorthy 放行 unknown / 收紧成只认 ok);仓库根 pnpm test:unit GATE_EXIT=0
(56 PASS / 0 FAIL);desktop typecheck 与 eslint 通过。

Signed-off-by: Dash <f9dftwf5tj@privaterelay.appleid.com>
Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings July 31, 2026 16:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (3)

apps/desktop/src/renderer/features/issue-tracker/IssueTrackerFeatureLayout.tsx:272

  • 这里的注释写“只有三路查询都成功、确实为空时…”,但 EmptyGuide 实际依赖的是 canTrustEmptyList(data) + hasFreshData 的判据;“三路都成功”容易被读成与底层通道健康状况强绑定(且该组件并未直接感知账本健康)。建议直接引用 canTrustEmptyList() 的语义,避免注释与实现口径漂移。
 * 标题分两版:只有三路查询都成功、确实为空时才敢说「还没有提交过 Issue」。任一路
 * 降级或失败时它就是一句错误断言 —— 用户在 GitHub 上有几十条 issue、只是这次没查到,
 * 页面却告诉他从没提交过(实际发生过,见 canTrustEmptyList)。

apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts:46

  • 这里的注释第一句写“只有三路都真查过且都成功才算”,但实现实际只基于 platform(degraded) 与 githubEnhancement 两个维度推断;“三路”容易被理解成把账本也纳入了可确证性判据,导致读者误判覆盖范围。建议把措辞改成“下面三个条件都满足”之类,避免把“条件数量”误写成“通道数量”。
/**
 * 列表是否**可被确证是完整的** —— 只有三路**都真查过且都成功**才算。
 *
 * 空态标题靠它决定能不能说「还没有提交过 Issue」。三个条件各有必要:

apps/desktop/src/main/github-issue/myIssuesRuntime.ts:172

  • PR 描述的“明确不包含”里写「配了插件但身份解析就失败仍走静默」,但当前实现里 resolveGithubEnhancement 在 ghostConfigured=true 且 gh token 缺失时会 throw;service 会把 enhancement.state 记为 failed 并通过 githubEnhancementFailed 触发 renderer 的 enhancementFailedGenericHint(即不再静默)。需要对齐:要么更新 PR 描述以反映新行为,要么调整实现让该分支仍保持静默(例如把身份解析失败归为 unknown/absent 并避免置 failed)。
 * **返回 null = 一条都没配(正常状态,静默);抛出 = 配了却问不出身份**(要提示,且不许拿
 * 缩水的结果覆盖首屏快照)。上一版把两条通道的身份失败都咽成 null,于是与「没配」不可
 * 区分:凭据过期 / 被撤销 / 通道超时 / GitHub 限流时,用户直接在 GitHub 提的那些 issue
 * 静静消失,页面一个字都不说,而缩水的结果还照样覆盖了完整快照。
 *
 * 判据是「**有没有通道配过**」,不是「哪一步报了错」——
 * `isCindyGithubGhostUsable` 含 `isGithubCredentialSaved()`,所以它为真就意味着用户确实
 * 存过 GitHub 凭据;「装了插件但从未授权」根本进不到这个分支(那时它为假)。同理 gh 那路
 * 以「有没有 token」判配没配。任一通道配过却一个身份都没拿到,就是配了用不上。
 *
 * (曾经错在这里:以为插件那步含糊、怕对「装了没授权」的用户误报,于是让它静默落到
 * 「没配」。但那种用户压根到不了这一步 —— 前提判错,结论也就跟着错。)
 */
async function resolveGithubEnhancement(): Promise<GithubEnhancementViewer | null> {
  const ghostDeps = getSharedGithubUserSubmitterDeps();
  // workdir 传 null:/issues 是全局页面,没有会话工作目录上下文。
  const ghostConfigured = isCindyGithubGhostUsable(ghostDeps, null);
  if (ghostConfigured) {
    const login = await readGhostViewerLogin(ghostDeps);
    // 插件问不出身份时不直接判死:gh CLI 可能有权限,下面照常再试一次。
    if (login) return { source: 'ghost', login };
  }

  const token = await getSharedGhCliTokenSource().readToken();
  if (!token) {
    // gh 这一路没配。插件那一路要是配过,说明「配了却一个身份都没拿到」⇒ 失败。
    if (ghostConfigured) {
      throw new Error('github enhancement identity lookup failed on every configured channel');
    }

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查通过,零 P0/P1。GitHub fallback 搜索与 snapshot 机制实现可靠。

@MagicLizi
MagicLizi merged commit 9b71977 into main Jul 31, 2026
13 checks passed
@MagicLizi
MagicLizi deleted the dash/issues-enhancement-fallback branch July 31, 2026 17:13
@MagicLizi

Copy link
Copy Markdown
Contributor

搜索回退到 gh CLI 这个兜底思路很实用——配了 GitHub 增强却搜不到东西的时候不再给用户一个空白列表装没事了。snapshot 的三态健康守卫写得干净 👌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants