-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(inline-comment): add confirmed param + probe-pattern safety net #1048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8b7fe18
cfce07a
5a4cfe6
0638d8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||
| #!/usr/bin/env node | ||||||||||||||||||||||||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||||||||||||||||||||||||
| import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||||||||||||||||||||||||
| import { appendFileSync } from "fs"; | ||||||||||||||||||||||||
| import { z } from "zod"; | ||||||||||||||||||||||||
| import { createOctokit } from "../github/api/client"; | ||||||||||||||||||||||||
| import { sanitizeContent } from "../github/utils/sanitizer"; | ||||||||||||||||||||||||
|
|
@@ -10,6 +11,12 @@ const REPO_OWNER = process.env.REPO_OWNER; | |||||||||||||||||||||||
| const REPO_NAME = process.env.REPO_NAME; | ||||||||||||||||||||||||
| const PR_NUMBER = process.env.PR_NUMBER; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Calls without confirmed=true are buffered here instead of posted. This | ||||||||||||||||||||||||
| // prevents subagents from posting test/probe comments when they inherit this | ||||||||||||||||||||||||
| // tool and probe it after hitting unrelated errors. The action's post-step | ||||||||||||||||||||||||
| // reports the buffer count for diagnostics. | ||||||||||||||||||||||||
| const BUFFER_PATH = "/tmp/inline-comments-buffer.jsonl"; | ||||||||||||||||||||||||
|
Comment on lines
+14
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment is misleading — overstates the buffering behavior. The comment says "Calls without confirmed=true are buffered here instead of posted", but that's not accurate. Calls with
Suggested change
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!REPO_OWNER || !REPO_NAME || !PR_NUMBER) { | ||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||
| "Error: REPO_OWNER, REPO_NAME, and PR_NUMBER environment variables are required", | ||||||||||||||||||||||||
|
|
@@ -67,8 +74,17 @@ server.tool( | |||||||||||||||||||||||
| .describe( | ||||||||||||||||||||||||
| "Specific commit SHA to comment on (defaults to latest commit)", | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| confirmed: z | ||||||||||||||||||||||||
| .boolean() | ||||||||||||||||||||||||
| .optional() | ||||||||||||||||||||||||
| .describe( | ||||||||||||||||||||||||
| "Set true to post the comment. When omitted, posts by default — " + | ||||||||||||||||||||||||
| "unless the body looks like a test/probe, in which case the call is " + | ||||||||||||||||||||||||
| "buffered and NOT posted. Set false to force buffering. Only set " + | ||||||||||||||||||||||||
| "true when posting final review comments.", | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| async ({ path, body, line, startLine, side, commit_id }) => { | ||||||||||||||||||||||||
| async ({ path, body, line, startLine, side, commit_id, confirmed }) => { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const githubToken = process.env.GITHUB_TOKEN; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -80,8 +96,6 @@ server.tool( | |||||||||||||||||||||||
| const repo = REPO_NAME; | ||||||||||||||||||||||||
| const pull_number = parseInt(PR_NUMBER, 10); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const octokit = createOctokit(githubToken).rest; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Sanitize the comment body to remove any potential GitHub tokens | ||||||||||||||||||||||||
| const sanitizedBody = sanitizeContent(body); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -92,10 +106,55 @@ server.tool( | |||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const looksLikeProbe = | ||||||||||||||||||||||||
| confirmed === undefined && | ||||||||||||||||||||||||
| /^\s*(test comment|testing if|probe\b|can i\b|does this work|just testing)/i.test( | ||||||||||||||||||||||||
| body, | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This pattern matches any comment starting with "Can I suggest..." or "Can improvements be made...", which are plausible review comment openings. Consider tightening to something like Also: the regex is inherently brittle — minor rewording ("trying this out", "hello world", "checking tool access") bypasses it entirely. If you keep the deny-by-default approach from the companion suggestion, this regex becomes a nice-to-have diagnostic rather than the critical safety gate. |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (confirmed === false || looksLikeProbe) { | ||||||||||||||||||||||||
| appendFileSync( | ||||||||||||||||||||||||
| BUFFER_PATH, | ||||||||||||||||||||||||
| JSON.stringify({ | ||||||||||||||||||||||||
| ts: new Date().toISOString(), | ||||||||||||||||||||||||
| path, | ||||||||||||||||||||||||
| line, | ||||||||||||||||||||||||
| startLine, | ||||||||||||||||||||||||
| side, | ||||||||||||||||||||||||
| body: sanitizedBody, | ||||||||||||||||||||||||
| reason: confirmed === false ? "confirmed=false" : "probe-pattern", | ||||||||||||||||||||||||
| }) + "\n", | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Comment on lines
+111
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If Consider wrapping the |
||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||
| content: [ | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| type: "text", | ||||||||||||||||||||||||
| text: JSON.stringify( | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| success: true, | ||||||||||||||||||||||||
| buffered: true, | ||||||||||||||||||||||||
| message: | ||||||||||||||||||||||||
| "Comment buffered (not posted). " + | ||||||||||||||||||||||||
| (looksLikeProbe | ||||||||||||||||||||||||
| ? "The body looks like a test/probe. " | ||||||||||||||||||||||||
| : "") + | ||||||||||||||||||||||||
| "Set confirmed=true to post. If you are testing whether " + | ||||||||||||||||||||||||
| "this tool works: it works — no need to test further.", | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| null, | ||||||||||||||||||||||||
| 2, | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
Comment on lines
+124
to
+144
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The buffered response coaches subagents on how to bypass the safety net. The message Consider removing the Alternatively (and more robustly): invert the default so that |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // If only line is provided, it's a single-line comment | ||||||||||||||||||||||||
| // If both startLine and line are provided, it's a multi-line comment | ||||||||||||||||||||||||
| const isSingleLine = !startLine; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const octokit = createOctokit(githubToken).rest; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const pr = await octokit.pulls.get({ | ||||||||||||||||||||||||
| owner, | ||||||||||||||||||||||||
| repo, | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Buffer contents are
cat'd without neutralizing workflow commands.The buffer body is user-influenced. Strings starting with
::(e.g.::warning::,::error::) are interpreted as workflow commands by the Actions runner. A crafted comment body could inject spurious warnings/errors into the log.Consider wrapping with
stop-commands: