From bfee3f6f09ec4eab8eb3ba7b8a7404c8c7f8e692 Mon Sep 17 00:00:00 2001 From: Mason Huang Date: Tue, 14 Apr 2026 09:11:57 +0800 Subject: [PATCH] fix(skills): use discussion target for notifications Use the discussion node ID for discussion_comment notifications and update the skill docs to reflect the notify target semantics. --- .../SKILL.md | 25 ++++---- .../scripts/secret-scanning.mjs | 57 +++++++++++++------ 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/.agents/skills/openclaw-secret-scanning-maintainer/SKILL.md b/.agents/skills/openclaw-secret-scanning-maintainer/SKILL.md index 648ff808c2f..7af2a1ceda7 100644 --- a/.agents/skills/openclaw-secret-scanning-maintainer/SKILL.md +++ b/.agents/skills/openclaw-secret-scanning-maintainer/SKILL.md @@ -64,16 +64,16 @@ The `fetch-content` output includes: ### Location type routing -| type | Flow | -| ----------------------------- | ------------------------ | -| `issue_comment` | Comment: delete+recreate | -| `pull_request_comment` | Comment: delete+recreate | -| `pull_request_review_comment` | Comment: delete+recreate | +| type | Flow | +| ----------------------------- | --------------------------------------------- | +| `issue_comment` | Comment: delete+recreate | +| `pull_request_comment` | Comment: delete+recreate | +| `pull_request_review_comment` | Comment: delete+recreate | | `discussion_comment` | Discussion comment: delete+recreate (GraphQL) | -| `issue_body` | Body: redact in place | -| `pull_request_body` | Body: redact in place | -| `commit` | Notify only | -| _other_ | Skip and report | +| `issue_body` | Body: redact in place | +| `pull_request_body` | Body: redact in place | +| `commit` | Notify only | +| _other_ | Skip and report | ## Step 2: Decide (Agent) @@ -102,6 +102,7 @@ node secret-scanning.mjs redact-body ### Comments — Delete and Recreate For issue/PR comments: + ```bash # Delete original (all edit history gone) node secret-scanning.mjs delete-comment @@ -111,6 +112,7 @@ node secret-scanning.mjs recreate-comment ``` For discussion comments (uses GraphQL): + ```bash # Delete original node secret-scanning.mjs delete-discussion-comment @@ -152,9 +154,12 @@ Cannot clean. Notify author to delete branch or force-push (for unmerged PRs). ## Step 5: Notify ```bash -node secret-scanning.mjs notify +node secret-scanning.mjs notify ``` +- For non-discussion types, `` is the issue/PR number. +- For `discussion_comment`, `` is the `discussion_node_id` returned by `fetch-content`. + Secret types are comma-separated: `"Discord Bot Token,Feishu App Secret"` The script picks the right template: diff --git a/.agents/skills/openclaw-secret-scanning-maintainer/scripts/secret-scanning.mjs b/.agents/skills/openclaw-secret-scanning-maintainer/scripts/secret-scanning.mjs index 9cbb1fbb21d..1ec116eb8f9 100644 --- a/.agents/skills/openclaw-secret-scanning-maintainer/scripts/secret-scanning.mjs +++ b/.agents/skills/openclaw-secret-scanning-maintainer/scripts/secret-scanning.mjs @@ -114,7 +114,8 @@ function cmdFetchContent(locationJson) { while (hasNextPage && !comment) { const afterClause = cursor ? `, after: "${cursor}"` : ""; - const gql = ghGraphQL(`{ + const gql = ghGraphQL( + `{ repository(owner: "${owner}", name: "${name}") { discussion(number: ${discussionNumber}) { id @@ -131,10 +132,15 @@ function cmdFetchContent(locationJson) { } } } - }`, { allowFailure: true }); + }`, + { allowFailure: true }, + ); const discussion = gql?.data?.repository?.discussion; - if (!discussion) fail(`Discussion #${discussionNumber} not found — it may have been deleted. The alert cannot be processed via this skill.`); + if (!discussion) + fail( + `Discussion #${discussionNumber} not found — it may have been deleted. The alert cannot be processed via this skill.`, + ); discussionId = discussion.id; comment = discussion.comments.nodes.find( @@ -144,7 +150,10 @@ function cmdFetchContent(locationJson) { cursor = discussion.comments.pageInfo.endCursor; } - if (!comment) fail(`Discussion comment #${discussionCommentDbId} not found in discussion #${discussionNumber}`); + if (!comment) + fail( + `Discussion comment #${discussionCommentDbId} not found in discussion #${discussionNumber}`, + ); const bodyFile = tmpFile("body.md"); fs.writeFileSync(bodyFile, comment.body || ""); @@ -356,7 +365,9 @@ function cmdDeleteComment(commentId) { */ function cmdDeleteDiscussionComment(nodeId) { if (!nodeId) fail("Usage: delete-discussion-comment "); - const result = ghGraphQL(`mutation { deleteDiscussionComment(input: { id: "${nodeId}" }) { comment { id } } }`); + const result = ghGraphQL( + `mutation { deleteDiscussionComment(input: { id: "${nodeId}" }) { comment { id } } }`, + ); if (result?.errors) { fail(`Failed to delete discussion comment: ${JSON.stringify(result.errors)}`); } @@ -368,13 +379,20 @@ function cmdDeleteDiscussionComment(nodeId) { * Create a new discussion comment via GraphQL. */ function cmdRecreateDiscussionComment(discussionNodeId, bodyFile) { - if (!discussionNodeId || !bodyFile) fail("Usage: recreate-discussion-comment "); + if (!discussionNodeId || !bodyFile) + fail("Usage: recreate-discussion-comment "); if (!fs.existsSync(bodyFile)) fail(`File not found: ${bodyFile}`); const body = fs.readFileSync(bodyFile, "utf8"); // Escape for GraphQL string literal - const escapedBody = body.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n"); - const result = ghGraphQL(`mutation { addDiscussionComment(input: { discussionId: "${discussionNodeId}", body: "${escapedBody}" }) { comment { id url } } }`); + const escapedBody = body + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\r/g, "\\r") + .replace(/\n/g, "\\n"); + const result = ghGraphQL( + `mutation { addDiscussionComment(input: { discussionId: "${discussionNodeId}", body: "${escapedBody}" }) { comment { id url } } }`, + ); if (result?.errors) { fail(`Failed to create discussion comment: ${JSON.stringify(result.errors)}`); } @@ -415,12 +433,13 @@ function cmdRecreateComment(issueNumber, bodyFile) { } /** - * notify + * notify * Post a notification comment with the correct template for the location type. + * target = issue/PR number for non-discussion types, discussion node ID for discussion_comment. */ -function cmdNotify(issueNumber, author, locationType, secretTypes) { - if (!issueNumber || !author || !locationType || !secretTypes) { - fail("Usage: notify "); +function cmdNotify(target, author, locationType, secretTypes) { + if (!target || !author || !locationType || !secretTypes) { + fail("Usage: notify "); } const types = secretTypes.split(",").map((s) => s.trim()); @@ -468,8 +487,14 @@ function cmdNotify(issueNumber, author, locationType, secretTypes) { // Discussion comments must be notified via GraphQL if (locationType === "discussion_comment") { - const escapedBody = body.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n"); - const result = ghGraphQL(`mutation { addDiscussionComment(input: { discussionId: "${issueNumber}", body: "${escapedBody}" }) { comment { id url } } }`); + const escapedBody = body + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\r/g, "\\r") + .replace(/\n/g, "\\n"); + const result = ghGraphQL( + `mutation { addDiscussionComment(input: { discussionId: "${target}", body: "${escapedBody}" }) { comment { id url } } }`, + ); if (result?.errors) { fail(`Failed to post discussion notification: ${JSON.stringify(result.errors)}`); } @@ -490,7 +515,7 @@ function cmdNotify(issueNumber, author, locationType, secretTypes) { const result = gh([ "api", - `repos/${REPO}/issues/${issueNumber}/comments`, + `repos/${REPO}/issues/${target}/comments`, "-X", "POST", "-F", @@ -660,7 +685,7 @@ if (!command || !commands[command]) { " delete-discussion-comment Delete a discussion comment (GraphQL)", " recreate-comment Create replacement comment", " recreate-discussion-comment Create discussion comment (GraphQL)", - " notify Post notification", + " notify Post notification", " resolve [resolution] [comment] Close alert", " list-open List open alerts", " summary Print formatted summary",