From 8675d4a2775cefdc8ca883095c395feffa50a3c9 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Tue, 4 Aug 2026 05:57:09 +0200 Subject: [PATCH 1/2] fix(issue-quality): require actionable reproduction detail --- .github/scripts/issue-quality.cjs | 87 +++++++++++++++++++++++++- .github/scripts/issue-quality.test.cjs | 49 +++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/.github/scripts/issue-quality.cjs b/.github/scripts/issue-quality.cjs index 9dccb742f8..b7779c6423 100644 --- a/.github/scripts/issue-quality.cjs +++ b/.github/scripts/issue-quality.cjs @@ -458,12 +458,92 @@ function isTooTerseFeatureSection(text) { } /** - * Bug Reproduction needs steps or concrete signals. A title-like phrase with - * no commands, paths, digits, or product keywords is not actionable. + * Bug Reproduction needs concrete signals that let a maintainer reproduce the + * failure. Product keywords alone (e.g. "choose model deepseek" or "send a + * message in the codex plugin") are not actionable: the report must name a + * command, an error, a file/config path, or an exact observed output. */ +// Commands and exact technical actions, e.g. "ocx start", "run bun", +// "send a streaming request", "curl https://...". +const REPRO_COMMAND_RE = new RegExp([ + "\\b(?:run|start|stop|restart|install|launch|execute|reproduce|trigger|invoke)\\s+(?:(?:the|an|a)\\s+)?(?:ocx|bun|npm|pnpm|yarn|curl|node|codex|proxy|server|dashboard|plugin)\\b", + "\\b(?:ocx|bun|npm|pnpm|yarn|curl|node|codex)\\s+(?:start|run|stop|restart|install|config|--[a-z-]+)\\b", + "\\b(?:send|issue|make|post)\\s+(?:a|an|any)\\s+(?:(?:[\\w.-]+\\s+)?request|command|prompt|query|call|api|curl|endpoint|url)\\b", + "\\b(?:pip|npm|bun)\\s+install\\b", + "\\b(?:curl|wget)\\s+[^\\s]+", +].join("|"), "i"); + +// Error, exception, and failure tokens, plus status codes in status context +// (bare 3-digit numbers can be ports or version numbers). +const REPRO_FAILURE_RE = new RegExp([ + "\\b(?:segfault|sigsegv|panic|abort|exception|traceback|stack\\s*trace|timeout|timed\\s*out|refused|reset|denied|failed?|error|crash|hang|hangs?|stuck|spinning|empty\\s*response)\\b", + "\\b(?:status\\s*(?:code\\s*)?|code\\s*|http\\s*)(?:is|of|:)?\\s*[1-5]\\d\\d\\b", +].join("|"), "i"); + +// File, config, and log paths such as ~/.codex/config.toml or C:\\logs\\ocx.log. +const REPRO_PATH_RE = new RegExp([ + "~?/[\\w.@-]+(?:/[\\w.@-]+)+", + "[A-Za-z]:\\\\(?:[\\w.@-]+\\\\)+[\\w.@-]+", + "~?/[\\w.@-]+/[\\w.@-]+\\.(?:json|yaml|yml|toml|conf|log|env|txt|ts|js|tsx|jsx|sh|ps1|py)", + "[\\w.@-]+\\.(?:json|yaml|yml|toml|conf|log|env)\\b", +].join("|")); +const ACTIONABLE_REPRO_RE = new RegExp( + [REPRO_COMMAND_RE.source, REPRO_FAILURE_RE.source, REPRO_PATH_RE.source].join("|"), + "i", +); + +// Sigil-only fences with no body content are never actionable. +const EMPTY_FENCE_RE = /^[ \t]{0,3}(?:```+|~~~+)\s*\n\s*\n[ \t]{0,3}(?:```+|~~~+)\s*$/; + +/** + * True when a bug Reproduction names commands, error tokens, file/config + * paths, or exact technical actions. Product/model mentions without any of + * those signals (e.g. #977) are treated as unactionable. Fenced blocks only + * count as actionable when their body contains non-whitespace content. + */ +function hasActionableReproductionDetail(text) { + const c = clean(text); + if (!c) return false; + if (ACTIONABLE_REPRO_RE.test(c)) return true; + // Fenced blocks: only count when the body has non-whitespace content. + if (/```|~~~/.test(c)) { + const parts = stripFencedActionableContent(c); + if (parts) return true; + } + return false; +} + +/** + * Walk the text looking for a fenced code block whose body contains + * non-whitespace content. Returns the non-empty body or null. + */ +function stripFencedActionableContent(text) { + const fenceRe = /^[ \t]{0,3}(`{3,}|~{3,})/; + const lines = text.split("\n"); + let i = 0; + while (i < lines.length) { + const m = lines[i].match(fenceRe); + if (!m) { i++; continue; } + const marker = m[1]; + const markerLen = marker.length; + // Find the closing fence on a later line. + let j = i + 1; + const endRe = new RegExp( + `^[ \\t]{0,3}${marker[0] === "`" ? "`" : "~"}{${markerLen},}[ \\t]*$`, + ); + while (j < lines.length && !endRe.test(lines[j])) j++; + if (j > i + 1) { + const body = lines.slice(i + 1, j).join("\n"); + if (body.trim()) return body; + } + i = j + 1; + } + return null; +} + function isTooTerseBugReproduction(text) { if (isEmpty(text) || isPlaceholder(text)) return false; - if (hasConcreteDetail(text)) return false; + if (hasActionableReproductionDetail(text)) return false; return countWords(text) < 12; } @@ -950,6 +1030,7 @@ module.exports = { isUnusableVersion, countWords, hasConcreteDetail, + hasActionableReproductionDetail, labelForKind, KIND_TO_LABEL, hasSubstantialStructuredContent, diff --git a/.github/scripts/issue-quality.test.cjs b/.github/scripts/issue-quality.test.cjs index c629a3d784..bcf093dccb 100644 --- a/.github/scripts/issue-quality.test.cjs +++ b/.github/scripts/issue-quality.test.cjs @@ -19,6 +19,7 @@ const { isUnusableVersion, countWords, hasConcreteDetail, + hasActionableReproductionDetail, rejectsWorkflowDispatchPullRequest, rejectsWorkflowDispatchNonDefaultBranch, } = require("./issue-quality.cjs"); @@ -462,6 +463,19 @@ describe("validateIssue - feature", () => { assert.ok(vagueResult.reasons.some((r) => r.includes("too vague"))); }); + it("treats only commands, errors, paths, or exact actions as actionable reproduction detail", () => { + assert.equal(hasActionableReproductionDetail("1. choose model deepseek\n2. send a message in codex plugin"), false); + assert.equal(hasActionableReproductionDetail("I want to work with deepseek in VSCode, but it dont reply"), false); + assert.equal(hasActionableReproductionDetail("1. ocx start --port 10100\n2. Send a request"), true); + assert.equal(hasActionableReproductionDetail("Run ocx start and send any streaming request."), true); + assert.equal(hasActionableReproductionDetail("ocx start on Raspberry Pi 4, send any streaming request."), true); + assert.equal(hasActionableReproductionDetail("The proxy returns HTTP 502 after the first streaming chunk."), true); + assert.equal(hasActionableReproductionDetail("Paste ~/.codex/config.toml, then restart the proxy."), true); + assert.equal(hasActionableReproductionDetail("```\n\n```"), false); + assert.equal(hasActionableReproductionDetail("~~~\n\n~~~"), false); + assert.equal(hasActionableReproductionDetail("```\nSIGSEGV at 0x0000\n```"), true); + }); + it("rejects fenced placeholder-only examples", () => { const fencedPlaceholders = [ "```\nN/A\n```", @@ -864,6 +878,41 @@ describe("validateIssue - bug", () => { assert.ok(result.reasons.some((r) => /Reproduction/i.test(r) && /vague/i.test(r))); }); + it("rejects a #977-shaped bug with product keywords but no actionable reproduction", () => { + const body = [ + "### Client or integration", + "Other", + "### Area", + "Proxy and routing", + "### Summary", + "I want to work with deepseek in VSCode, but it dont reply,just thinking", + "### Reproduction", + "1.choose model deepseek", + "2.send a message in codex plugin", + "### Version", + "2.10.0", + "### Operating system", + "Ubuntu 24.04", + "### Provider and model", + "deepseek", + ].join("\n"); + const result = validateIssue({ + title: "Dont work in VSCode Codex plugin", + body, + labels: ["bug", "proxy"], + }); + assert.equal(result.kind, "bug"); + assert.equal(result.valid, false); + assert.ok( + result.reasons.some((r) => /Reproduction/i.test(r) && /vague/i.test(r)), + `Expected a vague Reproduction reason, got: ${result.reasons.join("; ")}`, + ); + assert.ok( + result.guidance.some((g) => /commands|steps/i.test(g)), + `Expected reproduction guidance, got: ${result.guidance.join("; ")}`, + ); + }); + it("rejects unknown Operating system stand-ins on the new bug form", () => { const body = [ "### Client or integration", From 3c547bebc6c9f7e0e716789c3465a38bd0483b1a Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Tue, 4 Aug 2026 06:35:20 +0200 Subject: [PATCH 2/2] fix(issue-quality): reject generic request-action phrases --- .github/scripts/issue-quality.cjs | 4 +++- .github/scripts/issue-quality.test.cjs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/scripts/issue-quality.cjs b/.github/scripts/issue-quality.cjs index b7779c6423..9988bad2a1 100644 --- a/.github/scripts/issue-quality.cjs +++ b/.github/scripts/issue-quality.cjs @@ -468,7 +468,9 @@ function isTooTerseFeatureSection(text) { const REPRO_COMMAND_RE = new RegExp([ "\\b(?:run|start|stop|restart|install|launch|execute|reproduce|trigger|invoke)\\s+(?:(?:the|an|a)\\s+)?(?:ocx|bun|npm|pnpm|yarn|curl|node|codex|proxy|server|dashboard|plugin)\\b", "\\b(?:ocx|bun|npm|pnpm|yarn|curl|node|codex)\\s+(?:start|run|stop|restart|install|config|--[a-z-]+)\\b", - "\\b(?:send|issue|make|post)\\s+(?:a|an|any)\\s+(?:(?:[\\w.-]+\\s+)?request|command|prompt|query|call|api|curl|endpoint|url)\\b", + "\\b(?:send|issue|make|post)\\s+(?:a|an|any)\\s+(?:streaming|api|http|json|completion|chat|config|auth|embedding|post|graphql|grpc)\\s+(?:request|call|command|prompt|query)\\b", + "\\b(?:send|issue|make|post)\\s+(?:a|an|any)\\s+(?:api|curl|endpoint|url)\\b", + "\\b(?:send|issue|make|post)\\s+(?:a|an|any)\\s+[\\w.-]+\\s+request\\s+to\\s+(?:the\\s+)?(?:endpoint|url|api|server|proxy|\\S+/\\S+)\\b", "\\b(?:pip|npm|bun)\\s+install\\b", "\\b(?:curl|wget)\\s+[^\\s]+", ].join("|"), "i"); diff --git a/.github/scripts/issue-quality.test.cjs b/.github/scripts/issue-quality.test.cjs index bcf093dccb..f49f8d724b 100644 --- a/.github/scripts/issue-quality.test.cjs +++ b/.github/scripts/issue-quality.test.cjs @@ -469,6 +469,12 @@ describe("validateIssue - feature", () => { assert.equal(hasActionableReproductionDetail("1. ocx start --port 10100\n2. Send a request"), true); assert.equal(hasActionableReproductionDetail("Run ocx start and send any streaming request."), true); assert.equal(hasActionableReproductionDetail("ocx start on Raspberry Pi 4, send any streaming request."), true); + assert.equal(hasActionableReproductionDetail("send a request"), false); + assert.equal(hasActionableReproductionDetail("make a call"), false); + assert.equal(hasActionableReproductionDetail("post a command"), false); + assert.equal(hasActionableReproductionDetail("make an API call"), true); + assert.equal(hasActionableReproductionDetail("send an HTTP request"), true); + assert.equal(hasActionableReproductionDetail("send a request to /v1/responses"), true); assert.equal(hasActionableReproductionDetail("The proxy returns HTTP 502 after the first streaming chunk."), true); assert.equal(hasActionableReproductionDetail("Paste ~/.codex/config.toml, then restart the proxy."), true); assert.equal(hasActionableReproductionDetail("```\n\n```"), false);