🐛 fix(vscode): 接管流程 getIssue 纳入兜底,补关闭工单与不存在工单的拒绝

Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph
This commit is contained in:
2026-08-26 17:19:03 +08:00
parent 0acfd04b54
commit 71fc38e0c5
+38 -13
View File
@@ -161,6 +161,18 @@ export async function handleHandoffStart(panel: KanbanWebviewPanel, issueNumber:
return 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 branch = str(state.branch)
const worktreePath = str(state.worktreePath) const worktreePath = str(state.worktreePath)
const sessions: HandoffSessions = { const sessions: HandoffSessions = {
@@ -386,18 +398,29 @@ export async function handleHandoffAccept(panel: KanbanWebviewPanel, issueNumber
return return
} }
// 前置复查:webview 按钮的显示条件(assignees 含 me)可能已经过期(工单被 // spinner / extracted 声明在 try 外面:getIssue、mkdtemp 任一失败都要靠
// 重新指派),用刚从 Gitea 拉到的最新 issue 再判一次 canAcceptHandoff // 「是否已创建」来决定 catch/finally 里能不能 dismiss/rm,不能假设已存在
const issue = await getIssue({ host, token, owner, repo, index: issueNumber }) let spinner: string | undefined
if (!issue || !canAcceptHandoff({ handoffAttachmentId: String(attachmentId), assignees: (issue.assignees ?? []).map(a => a.login) }, me)) { let extracted: string | undefined
toast(panel, 'error', `#${issueNumber} 未指派给你,不能接管`)
finish()
return
}
const spinner = toast(panel, 'info', `正在接管 #${issueNumber}`, { spinner: true, dismissOnTimer: 120_000 })
const extracted = await scratchDir('spx-takeover-')
try { 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 meta = await getIssueAttachment({ host, token, owner, repo, index: issueNumber, attachmentId })
const data = await downloadAttachment({ token, url: meta.browser_download_url }) const data = await downloadAttachment({ token, url: meta.browser_download_url })
@@ -454,13 +477,15 @@ export async function handleHandoffAccept(panel: KanbanWebviewPanel, issueNumber
catch (err) { catch (err) {
const message = err instanceof Error ? err.message : String(err) const message = err instanceof Error ? err.message : String(err)
logger.add({ level: 'error', source: 'panel', message: `接管 #${issueNumber} 失败`, details: message }) logger.add({ level: 'error', source: 'panel', message: `接管 #${issueNumber} 失败`, details: message })
dismiss(panel, spinner) if (spinner)
dismiss(panel, spinner)
toast(panel, 'error', `接管失败:${message}`) toast(panel, 'error', `接管失败:${message}`)
} }
finally { finally {
// finish 先发:下面的 rm 失败不该让弹窗的 spinner 卡住不收。 // finish 先发:下面的 rm 失败不该让弹窗的 spinner 卡住不收。
finish() finish()
// rm 失败不该抛出掩盖上面 try/catch 已经判定好的成败——scratch 目录只是垃圾。 // rm 失败不该抛出掩盖上面 try/catch 已经判定好的成败——scratch 目录只是垃圾。
await fsp.rm(extracted, { recursive: true, force: true }).catch(() => {}) if (extracted)
await fsp.rm(extracted, { recursive: true, force: true }).catch(() => {})
} }
} }