From 71fc38e0c5692469702bbca070f50e6ea412749f Mon Sep 17 00:00:00 2001 From: cruldra Date: Wed, 26 Aug 2026 17:19:03 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(vscode):=20=E6=8E=A5?= =?UTF-8?q?=E7=AE=A1=E6=B5=81=E7=A8=8B=20getIssue=20=E7=BA=B3=E5=85=A5?= =?UTF-8?q?=E5=85=9C=E5=BA=95=EF=BC=8C=E8=A1=A5=E5=85=B3=E9=97=AD=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E4=B8=8E=E4=B8=8D=E5=AD=98=E5=9C=A8=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E7=9A=84=E6=8B=92=E7=BB=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph --- vscode/src/panel/handlers/handoffFlow.ts | 51 ++++++++++++++++++------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/vscode/src/panel/handlers/handoffFlow.ts b/vscode/src/panel/handlers/handoffFlow.ts index 75d5c92..f49ec72 100644 --- a/vscode/src/panel/handlers/handoffFlow.ts +++ b/vscode/src/panel/handlers/handoffFlow.ts @@ -161,6 +161,18 @@ export async function handleHandoffStart(panel: KanbanWebviewPanel, issueNumber: return } + // column 兜底成 'todo' 时挡不住「Gitea 已关闭但从没拖进看板」的工单—— + // 再拉一次远端 issue.state 兜底。 + const issue = await getIssue({ host, token, owner, repo, index: issueNumber }) + if (!issue) { + toast(panel, 'error', `工单 #${issueNumber} 不存在或已删除`) + return + } + if (issue.state === 'closed') { + toast(panel, 'error', `#${issueNumber} 已关闭,不能移交`) + return + } + const branch = str(state.branch) const worktreePath = str(state.worktreePath) const sessions: HandoffSessions = { @@ -386,18 +398,29 @@ export async function handleHandoffAccept(panel: KanbanWebviewPanel, issueNumber return } - // 前置复查:webview 按钮的显示条件(assignees 含 me)可能已经过期(工单被 - // 重新指派),用刚从 Gitea 拉到的最新 issue 再判一次 canAcceptHandoff。 - const issue = await getIssue({ host, token, owner, repo, index: issueNumber }) - if (!issue || !canAcceptHandoff({ handoffAttachmentId: String(attachmentId), assignees: (issue.assignees ?? []).map(a => a.login) }, me)) { - toast(panel, 'error', `#${issueNumber} 未指派给你,不能接管`) - finish() - return - } - - const spinner = toast(panel, 'info', `正在接管 #${issueNumber}…`, { spinner: true, dismissOnTimer: 120_000 }) - const extracted = await scratchDir('spx-takeover-') + // spinner / extracted 声明在 try 外面:getIssue、mkdtemp 任一失败都要靠 + // 「是否已创建」来决定 catch/finally 里能不能 dismiss/rm,不能假设已存在。 + let spinner: string | undefined + let extracted: string | undefined try { + spinner = toast(panel, 'info', `正在接管 #${issueNumber}…`, { spinner: true, dismissOnTimer: 120_000 }) + + // 前置复查:webview 按钮的显示条件(assignees 含 me)可能已经过期(工单被 + // 重新指派、被删),用刚从 Gitea 拉到的最新 issue 再判一次 canAcceptHandoff。 + const issue = await getIssue({ host, token, owner, repo, index: issueNumber }) + if (!issue) { + dismiss(panel, spinner) + toast(panel, 'error', `工单 #${issueNumber} 不存在或已删除`) + return + } + if (!canAcceptHandoff({ handoffAttachmentId: String(attachmentId), assignees: (issue.assignees ?? []).map(a => a.login) }, me)) { + dismiss(panel, spinner) + toast(panel, 'error', `#${issueNumber} 未指派给你,不能接管`) + return + } + + extracted = await scratchDir('spx-takeover-') + // ① 下载 + 解包 + 校验清单 const meta = await getIssueAttachment({ host, token, owner, repo, index: issueNumber, attachmentId }) const data = await downloadAttachment({ token, url: meta.browser_download_url }) @@ -454,13 +477,15 @@ export async function handleHandoffAccept(panel: KanbanWebviewPanel, issueNumber catch (err) { const message = err instanceof Error ? err.message : String(err) logger.add({ level: 'error', source: 'panel', message: `接管 #${issueNumber} 失败`, details: message }) - dismiss(panel, spinner) + if (spinner) + dismiss(panel, spinner) toast(panel, 'error', `接管失败:${message}`) } finally { // finish 先发:下面的 rm 失败不该让弹窗的 spinner 卡住不收。 finish() // rm 失败不该抛出掩盖上面 try/catch 已经判定好的成败——scratch 目录只是垃圾。 - await fsp.rm(extracted, { recursive: true, force: true }).catch(() => {}) + if (extracted) + await fsp.rm(extracted, { recursive: true, force: true }).catch(() => {}) } }