From 6820b6aff95fd843d8ac6cdeb27c4db76f0afbf2 Mon Sep 17 00:00:00 2001 From: heni02 Date: Wed, 15 Jul 2026 19:06:07 +0800 Subject: [PATCH] fix(robot): stop false Close-Issue-Without-PR WeChat alerts Notify only when send=yes; convert reopen-without-PR to composite so self-hosted runners can actually run the check. Co-authored-by: Cursor --- .github/workflows/robot.yaml | 9 ++-- actions/reopen-without-PR/action.yml | 39 +++++++++++++--- actions/reopen-without-PR/main.go | 52 ++++++++++++++++------ actions/reopen-without-PR/pkg/http/http.go | 2 + 4 files changed, 81 insertions(+), 21 deletions(-) diff --git a/.github/workflows/robot.yaml b/.github/workflows/robot.yaml index 44f66ccb..6f92b2d7 100644 --- a/.github/workflows/robot.yaml +++ b/.github/workflows/robot.yaml @@ -325,11 +325,14 @@ jobs: repository: ${{ github.repository }} # 获取执行该作业的仓库名 形如: matrixorigin/CI issue_number: ${{ github.event.issue.number }} # 获取当前操作的issue number assignees: "sukki37" #issue指定分配对象 - LABELS: "no-pr-linked" #相关自定义label配置 形如 label1,label2 - LABELS_NEED: "kind/bug,Bug fix,bug_fix,bug/ut" # 指定需要排查的标签 label1,label2,... + labels: "no-pr-linked" #相关自定义label配置 形如 label1,label2 + labels_need: "kind/bug,Bug fix,bug_fix,bug/ut" # 指定需要排查的标签 label1,label2,... uses: matrixorigin/CI/actions/reopen-without-PR@main - name: WeChat Work notification - if: ${{ failure() || cancelled() || steps.step1.outputs.send == 'yes' }} + # Only notify when the action confirms no linked PR (send=yes). + # Do NOT notify on failure/cancelled — runner/action errors were + # falsely alerting "Close Issue Without PR" for issues that already have PRs. + if: ${{ steps.step1.outputs.send == 'yes' }} id: notification uses: chf007/action-wechat-work@master env: diff --git a/actions/reopen-without-PR/action.yml b/actions/reopen-without-PR/action.yml index c87f5bf0..b56467c6 100644 --- a/actions/reopen-without-PR/action.yml +++ b/actions/reopen-without-PR/action.yml @@ -27,18 +27,47 @@ inputs: required: false default: "sukki77,fengttt,aressu1985" issue_owner: - require: true + required: true description: issue owner close_user: description: close issue user - require: true + required: true + base_url: + description: GitHub API base URL + required: false + default: 'https://api.github.com' outputs: send: description: Whether to send a notification + value: ${{ steps.run.outputs.send }} runs: - using: docker - image: Dockerfile + using: composite + steps: + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: ${{ github.action_path }}/go.mod + cache: false + + - name: Run reopen-without-PR + id: run + shell: bash + working-directory: ${{ github.action_path }} + env: + INPUT_BASE_URL: ${{ inputs.base_url }} + INPUT_REPOSITORY: ${{ inputs.repository }} + INPUT_ISSUE_NUMBER: ${{ inputs.issue_number }} + INPUT_GITHUB_TOKEN: ${{ inputs.github_token }} + INPUT_ASSIGNEES: ${{ inputs.assignees }} + INPUT_LABELS: ${{ inputs.labels }} + INPUT_LABELS_NEED: ${{ inputs.labels_need }} + INPUT_WHITELIST: ${{ inputs.whitelist }} + INPUT_ISSUE_OWNER: ${{ inputs.issue_owner }} + INPUT_CLOSE_USER: ${{ inputs.close_user }} + run: | + set -euo pipefail + CGO_ENABLED=0 go run . branding: icon: arrow-up - color: blue \ No newline at end of file + color: blue diff --git a/actions/reopen-without-PR/main.go b/actions/reopen-without-PR/main.go index 349ea7dc..054e9646 100644 --- a/actions/reopen-without-PR/main.go +++ b/actions/reopen-without-PR/main.go @@ -67,7 +67,11 @@ func task() { // 转交给sukki37 assigneeURL := fmt.Sprintf("%s/repos/%s/issues/%d/assignees", baseURL, repo, issueNumber) - assigneeData := map[string]string{"assignees": assignees} + assigneeList := strings.Split(assignees, ",") + for i := range assigneeList { + assigneeList[i] = strings.TrimSpace(assigneeList[i]) + } + assigneeData := map[string][]string{"assignees": assigneeList} jsonData, err = json.Marshal(assigneeData) if err != nil { panic("json.Marshal assigneeData failed") @@ -77,7 +81,9 @@ func task() { panic(fmt.Sprintf("Error creating assignee request:%v", err)) } if assigneeResp.StatusCode != 201 { - panic(fmt.Sprintf("Failed to assign a problem to a specified person,assigneeStatusCode:%d", assigneeResp.StatusCode)) + body, _ := io.ReadAll(assigneeResp.Body) + assigneeResp.Body.Close() + panic(fmt.Sprintf("Failed to assign a problem to a specified person,assigneeStatusCode:%d body:%s", assigneeResp.StatusCode, string(body))) } defer assigneeResp.Body.Close() fmt.Printf("Issue labeled and assigned to %s successfully.\n", assignees) @@ -201,14 +207,15 @@ func main() { panic(fmt.Sprintf("issueUrl http.Request failed,err:%v", err)) } if issueResp.StatusCode != http.StatusOK { - panic(fmt.Sprintf("connect issueUrl failed,resp.statusCode:%d", issueResp.StatusCode)) + body, _ := io.ReadAll(issueResp.Body) + issueResp.Body.Close() + panic(fmt.Sprintf("connect issueUrl failed,resp.statusCode:%d body:%s", issueResp.StatusCode, string(body))) } - defer issueResp.Body.Close() fmt.Printf("get issue %d info successfully.\n", issueNumber) - // 解析响应内容(这里省略了具体的解析过程,你可以根据需要自行处理) // 检查issue是否有关联的pull request body, err := io.ReadAll(issueResp.Body) + issueResp.Body.Close() if err != nil { panic(fmt.Sprintf("Error reading issue response body:%v", err)) } @@ -224,16 +231,35 @@ func main() { } for _, data := range issueData { - dataMap := data.(map[string]interface{}) - typeEvent := dataMap["event"] - if typeEvent == "cross-referenced" { - sourceMap := dataMap["source"].(map[string]interface{}) - issueMap := sourceMap["issue"].(map[string]interface{}) - _, ok := issueMap["pull_request"] - if ok { + dataMap, ok := data.(map[string]interface{}) + if !ok { + continue + } + switch dataMap["event"] { + case "cross-referenced": + // PR/issue that mentioned this issue in body or commit. + sourceMap, _ := dataMap["source"].(map[string]interface{}) + issueMap, _ := sourceMap["issue"].(map[string]interface{}) + if _, ok := issueMap["pull_request"]; ok { hasRelatedPR = true - break } + case "connected": + // Linked via Development sidebar (Fixes/Closes) or manual link. + // subject may be an issue or a PR; only count PRs. + if subject, ok := dataMap["subject"].(map[string]interface{}); ok { + if _, isPR := subject["pull_request"]; isPR { + hasRelatedPR = true + } + } else if source, ok := dataMap["source"].(map[string]interface{}); ok { + if issueMap, ok := source["issue"].(map[string]interface{}); ok { + if _, isPR := issueMap["pull_request"]; isPR { + hasRelatedPR = true + } + } + } + } + if hasRelatedPR { + break } } if hasRelatedPR { diff --git a/actions/reopen-without-PR/pkg/http/http.go b/actions/reopen-without-PR/pkg/http/http.go index e3ae7c67..b136e546 100644 --- a/actions/reopen-without-PR/pkg/http/http.go +++ b/actions/reopen-without-PR/pkg/http/http.go @@ -16,6 +16,8 @@ func Request(method, url, token string, data io.Reader, contentType string) (res return nil, err } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("X-GitHub-Api-Version", "2022-11-28") if contentType != "" { req.Header.Set("Content-Type", contentType) }