Improve bug report triage requirements

This commit is contained in:
tapframe 2026-06-11 01:59:22 +05:30
parent ee5c923091
commit ed7da63eb2
2 changed files with 105 additions and 17 deletions

View file

@ -9,10 +9,11 @@ body:
value: |
Thanks for reporting a bug.
If we can reproduce it, we can usually fix it. This form is just to get the basics in one place.
If we can reproduce it, we can usually fix it. Please describe the bug in enough detail that someone else can understand what failed without watching a video or guessing from the title.
Please replace the default title with a short summary of the actual problem.
If the app crashes, logs are required. Crash reports without logs may be labeled `needs-info`.
Vague reports such as "not working", "broken", or "same issue" may be labeled `needs-info` and closed if the missing details are not added.
If the app crashes or force closes, logs are required. Crash reports without logs will be closed.
- type: markdown
attributes:
@ -121,11 +122,26 @@ body:
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug description
description: |
Explain the problem in detail. Do not only write "not working" or "same issue".
Include what screen/flow you were using, what you tapped or tried to do, what changed on screen, and any visible error text.
placeholder: |
I was trying to ...
The app ...
I saw ...
This happens on ...
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: Exact steps. If it depends on specific content, describe it (movie/series, season/episode, source/addon name) without sharing private links.
description: Exact steps, one action per line. If it depends on specific content, describe it (movie/series, season/episode, source/addon name) without sharing private links.
placeholder: |
1. Open ...
2. Navigate to ...
@ -146,7 +162,7 @@ body:
id: actual
attributes:
label: Actual behavior
placeholder: "What actually happened (include any on-screen error text/codes)."
placeholder: "What actually happened? Include any on-screen error text/codes and whether the app froze, crashed, ignored input, showed the wrong content, etc."
validations:
required: true
@ -197,7 +213,7 @@ body:
attributes:
label: Logs (required for crash reports)
description: |
Required if the app crashes or force closes.
Required if the app crashes or force closes. Crash reports without logs will be closed.
For other bug reports, logs are optional but still helpful.
**Android:** `adb logcat -d | tail -n 300`

View file

@ -55,10 +55,37 @@ jobs:
return (value || "").replace(/\s+/g, " ").trim();
}
function normalizedMeaningfulText(value) {
return normalizeText(value)
.replace(/^```[a-zA-Z0-9_-]*/i, "")
.replace(/```$/i, "")
.trim();
}
function stripIssuePrefix(value) {
return normalizeText(value).replace(/^\[[^\]]+\]:\s*/i, "").trim();
}
function isEmptyish(value) {
const text = normalizedMeaningfulText(value);
return !text || /^(_?no response_?|n\/a|na|none|no|not available|-|\.)$/i.test(text);
}
function isVague(value) {
const text = normalizedMeaningfulText(value);
return /^(same|same issue|me too|not working|doesn't work|doesnt work|broken|bug|issue|problem|help|please fix|crash|crashes|it crashes|see video|see screenshot|as title)$/i.test(text);
}
function hasUsefulText(value, minimumLength) {
const text = normalizedMeaningfulText(value);
return !isEmptyish(text) && !isVague(text) && text.length >= minimumLength;
}
const description = extractFirstSection([
"Bug description",
"Describe the bug",
"Detailed description",
]);
const steps = extractSection("Steps to reproduce");
const expected = extractSection("Expected behavior");
const actual = extractSection("Actual behavior");
@ -69,7 +96,7 @@ jobs:
const extra = extractSection("Anything else? (optional)");
const summaryTitle = stripIssuePrefix(title);
const looksLikeBugForm = !!(steps || expected || actual);
const looksLikeBugForm = !!(description || steps || expected || actual);
const isBugIssue = hasLabel("bug") || looksLikeBugForm;
const isFeatureIssue =
hasLabel("enhancement") ||
@ -84,20 +111,20 @@ jobs:
const genericTitle = /^(bug|issue|problem|help|question|crash|broken|error|bug report|short summary here|title here)$/i;
const numericOnlyTitle = /^#?\d+$/;
const crashPattern = /\b(crash|crashes|crashed|crashing|force close|force closes|force closed|fatal exception|app closes|app closed unexpectedly)\b/i;
const crashContext = [summaryTitle, steps, actual, extra].map(normalizeText).join("\n");
const crashContext = [summaryTitle, description, steps, actual, extra].map(normalizeText).join("\n");
const isCrashIssue = crashPattern.test(crashContext);
const normalizedLogs = normalizeText(logs);
const hasLogs = normalizedLogs.length >= 20 && !/^(n\/a|na|none|no|not available)$/i.test(normalizedLogs);
const normalizedLogs = normalizedMeaningfulText(logs);
const hasLogs = normalizedLogs.length >= 20 && !isEmptyish(normalizedLogs);
if (!summaryTitle || summaryTitle.length < 8 || genericTitle.test(summaryTitle) || numericOnlyTitle.test(summaryTitle)) {
problems.push("Issue title (replace the default `[Bug]:` prefix with a short summary of the actual problem)");
}
if (!steps || steps.length < 30) problems.push("Steps to reproduce (please list exact steps)");
if (!expected || expected.length < 10) problems.push("Expected behavior");
if (!actual || actual.length < 10) problems.push("Actual behavior (include any on-screen error text)");
if (isCrashIssue && !hasLogs) {
problems.push("Logs (required for crash reports; include a log snippet or stack trace)");
if (!hasUsefulText(description, 50)) {
problems.push("Bug description (explain the problem in detail, not just \"not working\" or \"same issue\")");
}
if (!hasUsefulText(steps, 40)) problems.push("Steps to reproduce (please list exact steps)");
if (!hasUsefulText(expected, 10)) problems.push("Expected behavior");
if (!hasUsefulText(actual, 20)) problems.push("Actual behavior (include any on-screen error text)");
async function ensureLabel(name, color, description) {
try {
@ -111,6 +138,50 @@ jobs:
const hasNeedsInfo = hasLabel(NEEDS_INFO);
if (isCrashIssue && !hasLogs) {
await ensureLabel(NEEDS_INFO, NEEDS_INFO_COLOR, NEEDS_INFO_DESC);
if (!hasNeedsInfo) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number,
labels: [NEEDS_INFO],
});
}
const marker = "<!-- nuvio-bot:crash-logs-required-close -->";
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const alreadyCommented = comments.some(c => (c.body || "").includes(marker));
if (!alreadyCommented) {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body:
`${marker}\n` +
`Closing this crash report because crash reports must include logs.\n\n` +
`Please open a new report, or ask us to reopen this one, with a log snippet or stack trace from around the crash.\n\n` +
`Useful examples:\n` +
`- Android: \`adb logcat -d | tail -n 300\`\n` +
`- iOS: crash log from Xcode Organizer or Console.app\n` +
`- Desktop: terminal/console output from around the crash`,
});
}
await github.rest.issues.update({
owner,
repo,
issue_number,
state: "closed",
state_reason: "not_planned",
});
return;
}
if (problems.length > 0) {
await ensureLabel(NEEDS_INFO, NEEDS_INFO_COLOR, NEEDS_INFO_DESC);
if (!hasNeedsInfo) {
@ -125,12 +196,13 @@ jobs:
const marker = "<!-- nuvio-bot:needs-info -->";
const commentBody =
`${marker}\n` +
`Thanks for the report. Could you add a bit more detail so we can reproduce it?\n\n` +
`Missing / too short:\n` +
`Thanks for the report. Could you add more detail so we can reproduce it?\n\n` +
`Missing / too vague:\n` +
problems.map(p => `- ${p}`).join("\n") +
`\n\n` +
`Use a specific title, for example: \`[Bug]: Playback freezes when switching audio tracks on iOS\`.\n` +
`${isCrashIssue ? `Crash reports must include logs.\n` : `Logs are optional for most issues, but they help a lot.\n`}`;
`Reports that only say "not working", "broken", or "same issue" are not enough to debug.\n` +
`Logs are optional for most issues, but they help a lot. Crash reports without logs are closed automatically.\n`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner,