fix(triage): extract barnacle workflow

This commit is contained in:
Vincent Koc
2026-04-25 17:41:23 -07:00
parent 5b80d0c15e
commit 2f6615d2ee
4 changed files with 1009 additions and 844 deletions

View File

@@ -5,7 +5,7 @@ on:
types: [opened, edited, labeled]
issue_comment:
types: [created]
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; no untrusted checkout or code execution
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; trusted base checkout only, no untrusted PR code execution
types: [opened, edited, synchronize, reopened, labeled]
env:
@@ -20,10 +20,15 @@ permissions: {}
jobs:
auto-response:
permissions:
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
@@ -36,852 +41,15 @@ jobs:
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Handle labeled items
- name: Run Barnacle auto-response
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
// Labels prefixed with "r:" are auto-response triggers.
const activePrLimit = 10;
const rules = [
{
label: "r: skill",
close: true,
message:
"Thanks for the contribution! New skills should be published on [ClawHub](https://clawhub.ai) for everyone to use. Were keeping the core lean on skills, so Im closing this out.",
},
{
label: "r: support",
close: true,
message:
"Please use [our support server](https://discord.gg/clawd) and ask in #help or #users-helping-users to resolve this, or follow the stuck FAQ at https://docs.openclaw.ai/help/faq#im-stuck-whats-the-fastest-way-to-get-unstuck.",
},
{
label: "r: no-ci-pr",
close: true,
message:
"Please don't make PRs for test failures on main.\n\n" +
"The team is aware of those and will handle them directly on the codebase, not only fixing the tests but also investigating what the root cause is. Having to sift through test-fix-PRs (including some that have been out of date for weeks...) on top of that doesn't help. There are already way too many PRs for humans to manage; please don't make the flood worse.\n\n" +
"Thank you.",
},
{
label: "r: too-many-prs",
close: true,
message:
`Closing this PR because the author has more than ${activePrLimit} active PRs in this repo. ` +
"Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit.",
},
{
label: "r: testflight",
close: true,
commentTriggers: ["testflight"],
message: "Not available, build from source.",
},
{
label: "r: third-party-extension",
close: true,
message:
"Please publish this as a third-party plugin on [ClawHub](https://clawhub.ai) instead of adding it to the core repo. Docs: https://docs.openclaw.ai/plugin and https://docs.openclaw.ai/tools/clawhub",
},
{
label: "r: moltbook",
close: true,
lock: true,
lockReason: "off-topic",
commentTriggers: ["moltbook"],
message:
"OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.",
},
];
const managedLabelSpecs = {
"r: skill": {
color: "5319E7",
description: "Auto-close: skills should be published on ClawHub, not added to core.",
},
"r: support": {
color: "0E8A16",
description: "Auto-close: support requests belong in Discord or support docs.",
},
"r: no-ci-pr": {
color: "D93F0B",
description: "Auto-close: PR only chasing known main CI/test failures.",
},
"r: too-many-prs": {
color: "D93F0B",
description: "Auto-close: author has more than ten active PRs.",
},
"r: too-many-prs-override": {
color: "C2E0C6",
description: "Maintainer override for the active-PR limit auto-close.",
},
"r: testflight": {
color: "D93F0B",
description: "Auto-close: TestFlight access/request issues are off-topic here.",
},
"r: third-party-extension": {
color: "5319E7",
description: "Auto-close: third-party plugins/capabilities belong on ClawHub.",
},
"r: moltbook": {
color: "B60205",
description: "Auto-close and lock: Moltbook is off-topic for OpenClaw.",
},
"r: spam": {
color: "B60205",
description: "Auto-close and lock spam.",
},
dirty: {
color: "B60205",
description: "Maintainer-applied auto-close for dirty/unrelated PR branches.",
},
"bad-barnacle": {
color: "E99695",
description: "Suppress Barnacle automation on this issue or PR.",
},
"trigger-response": {
color: "FBCA04",
description: "Maintainer trigger to rerun Barnacle auto-response on an item.",
},
"triage: low-signal-docs": {
color: "C5DEF5",
description: "Candidate: docs-only change looks low signal; maintainer review needed.",
},
"triage: docs-discoverability": {
color: "C5DEF5",
description: "Candidate: docs discoverability/listing change may belong elsewhere.",
},
"triage: test-only-no-bug": {
color: "C5DEF5",
description: "Candidate: test-only change has no linked bug or behavior evidence.",
},
"triage: refactor-only": {
color: "C5DEF5",
description: "Candidate: refactor/cleanup-only PR without maintainer context.",
},
"triage: blank-template": {
color: "C5DEF5",
description: "Candidate: PR template appears mostly untouched.",
},
"triage: dirty-candidate": {
color: "C5DEF5",
description: "Candidate: broad unrelated surfaces; may need splitting or cleanup.",
},
"triage: risky-infra": {
color: "C5DEF5",
description: "Candidate: infra/CI/release change needs maintainer review.",
},
"triage: external-plugin-candidate": {
color: "C5DEF5",
description: "Candidate: plugin/capability may belong on ClawHub.",
},
};
const maintainerTeam = "maintainer";
const pingWarningMessage =
"Please dont spam-ping multiple maintainers at once. Be patient, or join our community Discord for help: https://discord.gg/clawd";
const mentionRegex = /@([A-Za-z0-9-]+)/g;
const maintainerCache = new Map();
const normalizeLogin = (login) => login.toLowerCase();
const bugSubtypeLabelSpecs = {
regression: {
color: "D93F0B",
description: "Behavior that previously worked and now fails",
},
"bug:crash": {
color: "B60205",
description: "Process/app exits unexpectedly or hangs",
},
"bug:behavior": {
color: "D73A4A",
description: "Incorrect behavior without a crash",
},
};
const bugTypeToLabel = {
"Regression (worked before, now fails)": "regression",
"Crash (process/app exits or hangs)": "bug:crash",
"Behavior bug (incorrect output/state without crash)": "bug:behavior",
};
const bugSubtypeLabels = Object.keys(bugSubtypeLabelSpecs);
const extractIssueFormValue = (body, field) => {
if (!body) {
return "";
}
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(
`(?:^|\\n)###\\s+${escapedField}\\s*\\n([\\s\\S]*?)(?=\\n###\\s+|$)`,
"i",
);
const match = body.match(regex);
if (!match) {
return "";
}
for (const line of match[1].split("\n")) {
const trimmed = line.trim();
if (trimmed) {
return trimmed;
}
}
return "";
};
const ensureLabelSynced = async (name, color, description) => {
try {
const current = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
});
const currentDescription = current.data.description ?? "";
if (
current.data.color.toLowerCase() !== color.toLowerCase() ||
currentDescription !== description
) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
color,
description,
});
}
} catch (error) {
if (error?.status !== 404) {
throw error;
}
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
color,
description,
});
}
};
const syncManagedLabels = async () => {
for (const [name, spec] of Object.entries(managedLabelSpecs)) {
await ensureLabelSynced(name, spec.color, spec.description);
}
};
const syncBugSubtypeLabel = async (issue, labelSet) => {
if (!labelSet.has("bug")) {
return;
}
const selectedBugType = extractIssueFormValue(issue.body ?? "", "Bug type");
const targetLabel = bugTypeToLabel[selectedBugType];
if (!targetLabel) {
return;
}
const targetSpec = bugSubtypeLabelSpecs[targetLabel];
await ensureLabelSynced(targetLabel, targetSpec.color, targetSpec.description);
for (const subtypeLabel of bugSubtypeLabels) {
if (subtypeLabel === targetLabel) {
continue;
}
if (!labelSet.has(subtypeLabel)) {
continue;
}
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: subtypeLabel,
});
labelSet.delete(subtypeLabel);
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
}
if (!labelSet.has(targetLabel)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: [targetLabel],
});
labelSet.add(targetLabel);
}
};
const isMaintainer = async (login) => {
if (!login) {
return false;
}
const normalized = normalizeLogin(login);
if (maintainerCache.has(normalized)) {
return maintainerCache.get(normalized);
}
let isMember = false;
try {
const membership = await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: maintainerTeam,
username: normalized,
});
isMember = membership?.data?.state === "active";
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
maintainerCache.set(normalized, isMember);
return isMember;
};
const countMaintainerMentions = async (body, authorLogin) => {
if (!body) {
return 0;
}
const normalizedAuthor = authorLogin ? normalizeLogin(authorLogin) : "";
if (normalizedAuthor && (await isMaintainer(normalizedAuthor))) {
return 0;
}
const haystack = body.toLowerCase();
const teamMention = `@${context.repo.owner.toLowerCase()}/${maintainerTeam}`;
if (haystack.includes(teamMention)) {
return 3;
}
const mentions = new Set();
for (const match of body.matchAll(mentionRegex)) {
mentions.add(normalizeLogin(match[1]));
}
if (normalizedAuthor) {
mentions.delete(normalizedAuthor);
}
let count = 0;
for (const login of mentions) {
if (await isMaintainer(login)) {
count += 1;
}
}
return count;
};
const triggerLabel = "trigger-response";
const activePrLimitLabel = "r: too-many-prs";
const activePrLimitOverrideLabel = "r: too-many-prs-override";
const target = context.payload.issue ?? context.payload.pull_request;
if (!target) {
return;
}
const labelSet = new Set(
(target.labels ?? [])
.map((label) => (typeof label === "string" ? label : label?.name))
.filter((name) => typeof name === "string"),
const { pathToFileURL } = require("node:url");
const moduleUrl = pathToFileURL(
`${process.env.GITHUB_WORKSPACE}/scripts/github/barnacle-auto-response.mjs`,
);
const { runBarnacleAutoResponse } = await import(moduleUrl.href);
const issue = context.payload.issue;
const pullRequest = context.payload.pull_request;
const comment = context.payload.comment;
if (comment) {
const authorLogin = comment.user?.login ?? "";
if (comment.user?.type === "Bot" || authorLogin.endsWith("[bot]")) {
return;
}
const commentBody = comment.body ?? "";
const responses = [];
const mentionCount = await countMaintainerMentions(commentBody, authorLogin);
if (mentionCount >= 3) {
responses.push(pingWarningMessage);
}
const commentHaystack = commentBody.toLowerCase();
const commentRule = rules.find((item) =>
(item.commentTriggers ?? []).some((trigger) =>
commentHaystack.includes(trigger),
),
);
if (commentRule) {
responses.push(commentRule.message);
}
if (responses.length > 0) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: target.number,
body: responses.join("\n\n"),
});
}
return;
}
if (issue) {
const action = context.payload.action;
if (action === "opened" || action === "edited") {
const issueText = `${issue.title ?? ""}\n${issue.body ?? ""}`.trim();
const authorLogin = issue.user?.login ?? "";
const mentionCount = await countMaintainerMentions(
issueText,
authorLogin,
);
if (mentionCount >= 3) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: pingWarningMessage,
});
}
await syncBugSubtypeLabel(issue, labelSet);
}
}
const hasTriggerLabel = labelSet.has(triggerLabel);
if (hasTriggerLabel) {
labelSet.delete(triggerLabel);
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: target.number,
name: triggerLabel,
});
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
}
const isLabelEvent = context.payload.action === "labeled";
const isPrCandidateEvent =
pullRequest &&
["opened", "edited", "synchronize", "reopened", "labeled"].includes(
context.payload.action,
);
if (!hasTriggerLabel && !isLabelEvent && !isPrCandidateEvent) {
return;
}
if (issue) {
const title = issue.title ?? "";
const body = issue.body ?? "";
const haystack = `${title}\n${body}`.toLowerCase();
const hasMoltbookLabel = labelSet.has("r: moltbook");
const hasTestflightLabel = labelSet.has("r: testflight");
const hasSecurityLabel = labelSet.has("security");
if (title.toLowerCase().includes("security") && !hasSecurityLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ["security"],
});
labelSet.add("security");
}
if (title.toLowerCase().includes("testflight") && !hasTestflightLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ["r: testflight"],
});
labelSet.add("r: testflight");
}
if (haystack.includes("moltbook") && !hasMoltbookLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ["r: moltbook"],
});
labelSet.add("r: moltbook");
}
}
const invalidLabel = "invalid";
const spamLabel = "r: spam";
const dirtyLabel = "dirty";
const badBarnacleLabel = "bad-barnacle";
const candidateLabels = {
blankTemplate: "triage: blank-template",
lowSignalDocs: "triage: low-signal-docs",
docsDiscoverability: "triage: docs-discoverability",
testOnlyNoBug: "triage: test-only-no-bug",
refactorOnly: "triage: refactor-only",
dirtyCandidate: "triage: dirty-candidate",
riskyInfra: "triage: risky-infra",
externalPluginCandidate: "triage: external-plugin-candidate",
};
const noisyPrMessage =
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
const hasLinkedReference = (text) =>
/(?:#\d+|github\.com\/openclaw\/openclaw\/(?:issues|pull)\/\d+)/i.test(
text,
);
const hasFilledTemplateLine = (body, field) => {
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(
`^\\s*-\\s*${escapedField}:\\s*\\S`,
"im",
);
return regex.test(body);
};
const hasMostlyBlankTemplate = (body) => {
if (!body) {
return true;
}
const emptyFields = [
"Problem",
"Why it matters",
"What changed",
"What did NOT change",
"Root cause",
"Target test or file",
].filter((field) => {
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(
`^\\s*-\\s*${escapedField}(?: \\([^)]*\\))?:\\s*$`,
"im",
);
return regex.test(body);
}).length;
const hasTemplateIntro = body.includes(
"Describe the problem and fix in 25 bullets",
);
const emptyClosingRef = /^\s*-\s*(?:Closes|Related)\s+#\s*$/im.test(
body,
);
return hasTemplateIntro && emptyFields >= 3 && emptyClosingRef;
};
const hasConcreteBehaviorContext = (body, text) => {
if (hasLinkedReference(text)) {
return true;
}
if (
hasFilledTemplateLine(body, "Problem") &&
hasFilledTemplateLine(body, "Why it matters") &&
hasFilledTemplateLine(body, "What changed")
) {
return true;
}
return /\b(repro|regression|root cause|crash|bug|failure|failing|broken|behavior|scenario|fixes?)\b/i.test(
text,
);
};
const hasClearDesignContext = (body, text) =>
hasConcreteBehaviorContext(body, text) ||
/\b(rfc|design|architecture|migration|maintainer request|owner request|requested by maintainer|approved by maintainer|beta blocker)\b/i.test(
text,
);
const isMarkdownOrDocsFile = (filename) =>
/^docs\//.test(filename) ||
/\.mdx?$/i.test(filename) ||
/(^|\/)(README|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE)\.md$/i.test(
filename,
);
const isTestLikeFile = (filename) =>
/(^|\/)(__tests__|fixtures?|snapshots?)(\/|$)/i.test(filename) ||
/(^|\/)test\/helpers\//i.test(filename) ||
/(^|\/)src\/test-utils\//i.test(filename) ||
/\.(?:test|spec)\.[cm]?[jt]sx?$/i.test(filename) ||
/\.(?:snap|snapshot)$/i.test(filename);
const isInfraLikeFile = (filename) =>
/^\.github\/(?:workflows|actions)\//.test(filename) ||
/^scripts\//.test(filename) ||
/^Dockerfile(?:\.|$)/.test(filename) ||
/^docker\//.test(filename) ||
/(^|\/)(?:package\.json|pnpm-lock\.yaml|pnpm-workspace\.yaml|bun\.lockb?|actionlint\.yaml|dependabot\.yml)$/i.test(
filename,
) ||
/\brelease\b/i.test(filename);
const surfacesForFile = (filename) => {
const surfaces = new Set();
if (/\.generated\/|generated|\.snap$/i.test(filename)) {
surfaces.add("generated");
}
if (filename.startsWith("ui/")) {
surfaces.add("ui");
} else if (filename.startsWith("src/gateway/")) {
surfaces.add("src/gateway");
} else if (filename.startsWith("src/plugins/")) {
surfaces.add("src/plugins");
} else if (filename.startsWith("extensions/")) {
surfaces.add("extensions");
} else if (filename.startsWith("apps/")) {
surfaces.add("apps");
} else if (filename.startsWith(".github/")) {
surfaces.add(".github");
} else if (filename.startsWith("docs/") || /\.mdx?$/i.test(filename)) {
surfaces.add("docs");
} else if (filename.startsWith("scripts/")) {
surfaces.add("scripts");
} else {
surfaces.add("other");
}
return [...surfaces];
};
const listPullRequestFiles = async (pullRequest) =>
github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pullRequest.number,
per_page: 100,
});
const addMissingLabels = async (issueNumber, labels, labelSet) => {
const missingLabels = labels.filter((label) => !labelSet.has(label));
if (missingLabels.length === 0) {
return;
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: missingLabels,
});
for (const label of missingLabels) {
labelSet.add(label);
}
core.info(`Added candidate labels to #${issueNumber}: ${missingLabels.join(", ")}`);
};
const applyPullRequestCandidateLabels = async (pullRequest, labelSet) => {
const files = await listPullRequestFiles(pullRequest);
if (files.length === 0) {
return;
}
const filenames = files.map((file) => file.filename);
const body = pullRequest.body ?? "";
const text = `${pullRequest.title ?? ""}\n${body}`;
const lowerText = text.toLowerCase();
const linkedReference = hasLinkedReference(text);
const blankTemplate = hasMostlyBlankTemplate(body);
const concreteBehaviorContext = hasConcreteBehaviorContext(body, text);
const clearDesignContext = hasClearDesignContext(body, text);
const labelsToAdd = [];
if (blankTemplate) {
labelsToAdd.push(candidateLabels.blankTemplate);
}
const docsOnly = filenames.every(isMarkdownOrDocsFile);
const docsSignal =
/\b(add|adds|update|updates|fix|fixes|improve|cleanup|clean up|typo|readme|docs?|documentation|translation|translate)\b/i.test(
text,
);
const discoverabilityDocs = filenames.some((filename) =>
/^(README(?:\.[^.]+)?\.md|docs\/plugins\/community\.md|docs\/start\/showcase\.md)$/i.test(
filename,
),
);
if (docsOnly && !linkedReference && (blankTemplate || docsSignal)) {
labelsToAdd.push(candidateLabels.lowSignalDocs);
}
if (
docsOnly &&
!linkedReference &&
(discoverabilityDocs ||
/\b(community plugin|plugin listing|discoverability|showcase|clawhub)\b/i.test(
text,
))
) {
labelsToAdd.push(candidateLabels.docsDiscoverability);
}
const testOnly = filenames.every(isTestLikeFile);
const lowSignalTestTitle =
/\b(add|adds|added|improve|increase|boost|expand|fix|stabilize|update)\b.*\b(test|tests|coverage|flaky|flake|snapshot|fixtures?)\b/i.test(
pullRequest.title ?? "",
) ||
/\b(test|tests|coverage|flaky|flake)\b.*\b(add|increase|improve|fix|update|stabilize)\b/i.test(
pullRequest.title ?? "",
);
if (
testOnly &&
!linkedReference &&
!concreteBehaviorContext &&
lowSignalTestTitle
) {
labelsToAdd.push(candidateLabels.testOnlyNoBug);
}
if (
!linkedReference &&
!concreteBehaviorContext &&
/\b(refactor|cleanup|clean up|rename|formatting|style-only|style only)\b/i.test(
text,
)
) {
labelsToAdd.push(candidateLabels.refactorOnly);
}
if (
filenames.every(isInfraLikeFile) &&
!linkedReference &&
!clearDesignContext
) {
labelsToAdd.push(candidateLabels.riskyInfra);
}
const addsPluginManifest = files.some(
(file) =>
file.status === "added" &&
/^extensions\/[^/]+\/openclaw\.plugin\.json$/i.test(
file.filename,
),
);
if (
!clearDesignContext &&
(addsPluginManifest ||
/\b(third[- ]party|external plugin|community plugin|clawhub)\b/i.test(
lowerText,
))
) {
labelsToAdd.push(candidateLabels.externalPluginCandidate);
}
const surfaces = new Set(filenames.flatMap(surfacesForFile));
if (surfaces.size >= 4 && !clearDesignContext) {
labelsToAdd.push(candidateLabels.dirtyCandidate);
}
await addMissingLabels(
pullRequest.number,
[...new Set(labelsToAdd)],
labelSet,
);
};
await syncManagedLabels();
if (pullRequest) {
if (labelSet.has(badBarnacleLabel)) {
core.info(`Skipping PR auto-response checks for #${pullRequest.number} because ${badBarnacleLabel} is present.`);
return;
}
await applyPullRequestCandidateLabels(pullRequest, labelSet);
if (labelSet.has(dirtyLabel)) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
body: noisyPrMessage,
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
state: "closed",
});
return;
}
if (labelSet.has(spamLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
state: "closed",
});
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
lock_reason: "spam",
});
return;
}
if (labelSet.has(invalidLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
state: "closed",
});
return;
}
}
if (issue && labelSet.has(spamLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: "closed",
state_reason: "not_planned",
});
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
lock_reason: "spam",
});
return;
}
if (issue && labelSet.has(invalidLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: "closed",
state_reason: "not_planned",
});
return;
}
if (pullRequest && labelSet.has(activePrLimitOverrideLabel)) {
labelSet.delete(activePrLimitLabel);
}
const rule = rules.find((item) => labelSet.has(item.label));
if (!rule) {
return;
}
const issueNumber = target.number;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: rule.message,
});
if (rule.close) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: "closed",
});
}
if (rule.lock) {
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
lock_reason: rule.lockReason ?? "resolved",
});
}
await runBarnacleAutoResponse({ github, context, core });