Skip to content

Commit 9f03054

Browse files
authored
docs: add beta blocker contributor guidance (#55199)
* docs: add beta blocker contributor guidance * fix: tighten beta blocker labeling and flaky config test
1 parent e403899 commit 9f03054

6 files changed

Lines changed: 233 additions & 14 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ body:
99
value: |
1010
Thanks for filing this report. Keep every answer concise, reproducible, and grounded in observed evidence.
1111
Do not speculate or infer beyond the evidence. If a narrative section cannot be answered from the available evidence, respond with exactly `NOT_ENOUGH_INFO`.
12+
13+
If this is a plugin beta-release blocker, rename the issue title to `Beta blocker: <plugin-name> - <summary>` and apply the `beta-blocker` label after filing.
1214
- type: dropdown
1315
id: bug_type
1416
attributes:
@@ -20,6 +22,19 @@ body:
2022
- Behavior bug (incorrect output/state without crash)
2123
validations:
2224
required: true
25+
- type: dropdown
26+
id: beta_blocker
27+
attributes:
28+
label: Beta release blocker
29+
description: >
30+
Choose `Yes` only if this blocks plugin compatibility during the current beta release window.
31+
Selecting `Yes` does not apply the label automatically. You must also rename the issue title
32+
to `Beta blocker: <plugin-name> - <summary>` for the automation to apply the `beta-blocker` label.
33+
options:
34+
- "No"
35+
- "Yes"
36+
validations:
37+
required: true
2338
- type: textarea
2439
id: summary
2540
attributes:

.github/pull_request_template.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Describe the problem and fix in 2–5 bullets:
44

5+
If this PR fixes a plugin beta-release blocker, title it `fix(<plugin-id>): beta blocker - <summary>` and link the matching `Beta blocker: <plugin-name> - <summary>` issue labeled `beta-blocker`. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.
6+
57
- Problem:
68
- Why it matters:
79
- What changed:

.github/workflows/labeler.yml

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Labeler
22

33
on:
44
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned triage workflow; no untrusted checkout or PR code execution
5-
types: [opened, synchronize, reopened]
5+
types: [opened, synchronize, reopened, edited]
66
issues:
7-
types: [opened]
7+
types: [opened, edited]
88
workflow_dispatch:
99
inputs:
1010
max_prs:
@@ -209,6 +209,59 @@ jobs:
209209
// labels: [trustedLabel],
210210
// });
211211
// }
212+
- name: Apply beta-blocker title label
213+
uses: actions/github-script@v8
214+
with:
215+
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
216+
script: |
217+
const pullRequest = context.payload.pull_request;
218+
if (!pullRequest) {
219+
return;
220+
}
221+
222+
const labelName = "beta-blocker";
223+
const matchesBetaBlocker = /\bbeta blocker\b/i.test(pullRequest.title ?? "");
224+
225+
try {
226+
await github.rest.issues.getLabel({
227+
owner: context.repo.owner,
228+
repo: context.repo.repo,
229+
name: labelName,
230+
});
231+
} catch (error) {
232+
if (error?.status !== 404) {
233+
throw error;
234+
}
235+
core.info(`Skipping ${labelName} labeling because the label does not exist in the repository.`);
236+
return;
237+
}
238+
239+
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
240+
owner: context.repo.owner,
241+
repo: context.repo.repo,
242+
issue_number: pullRequest.number,
243+
per_page: 100,
244+
});
245+
const hasLabel = currentLabels.some((label) => label.name === labelName);
246+
247+
if (matchesBetaBlocker && !hasLabel) {
248+
await github.rest.issues.addLabels({
249+
owner: context.repo.owner,
250+
repo: context.repo.repo,
251+
issue_number: pullRequest.number,
252+
labels: [labelName],
253+
});
254+
return;
255+
}
256+
257+
if (!matchesBetaBlocker && hasLabel) {
258+
await github.rest.issues.removeLabel({
259+
owner: context.repo.owner,
260+
repo: context.repo.repo,
261+
issue_number: pullRequest.number,
262+
name: labelName,
263+
});
264+
}
212265
- name: Apply too-many-prs label
213266
uses: actions/github-script@v8
214267
with:
@@ -419,6 +472,7 @@ jobs:
419472
const maxCount = processAll ? Number.POSITIVE_INFINITY : Math.max(1, maxPrs);
420473
421474
const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
475+
const betaBlockerLabel = "beta-blocker";
422476
const labelColor = "b76e79";
423477
// const trustedLabel = "trusted-contributor";
424478
// const experiencedLabel = "experienced-contributor";
@@ -449,6 +503,22 @@ jobs:
449503
}
450504
}
451505
506+
async function hasBetaBlockerLabel() {
507+
try {
508+
await github.rest.issues.getLabel({
509+
owner,
510+
repo,
511+
name: betaBlockerLabel,
512+
});
513+
return true;
514+
} catch (error) {
515+
if (error?.status !== 404) {
516+
throw error;
517+
}
518+
return false;
519+
}
520+
}
521+
452522
async function resolveContributorLabel(login) {
453523
if (contributorCache.has(login)) {
454524
return contributorCache.get(login);
@@ -580,7 +650,37 @@ jobs:
580650
labelNames.add(label);
581651
}
582652
653+
async function applyBetaBlockerTitleLabel(pullRequest, labelNames) {
654+
const matchesBetaBlocker = /\bbeta blocker\b/i.test(pullRequest.title ?? "");
655+
656+
if (matchesBetaBlocker) {
657+
if (!labelNames.has(betaBlockerLabel)) {
658+
await github.rest.issues.addLabels({
659+
owner,
660+
repo,
661+
issue_number: pullRequest.number,
662+
labels: [betaBlockerLabel],
663+
});
664+
labelNames.add(betaBlockerLabel);
665+
}
666+
return;
667+
}
668+
669+
if (!labelNames.has(betaBlockerLabel)) {
670+
return;
671+
}
672+
673+
await github.rest.issues.removeLabel({
674+
owner,
675+
repo,
676+
issue_number: pullRequest.number,
677+
name: betaBlockerLabel,
678+
});
679+
labelNames.delete(betaBlockerLabel);
680+
}
681+
583682
await ensureSizeLabels();
683+
const betaBlockerLabelExists = await hasBetaBlockerLabel();
584684
585685
let page = 1;
586686
let processed = 0;
@@ -618,6 +718,9 @@ jobs:
618718
619719
await applySizeLabel(pullRequest, currentLabels, labelNames);
620720
await applyContributorLabel(pullRequest, labelNames);
721+
if (betaBlockerLabelExists) {
722+
await applyBetaBlockerTitleLabel(pullRequest, labelNames);
723+
}
621724
622725
processed += 1;
623726
}
@@ -719,3 +822,56 @@ jobs:
719822
// labels: [trustedLabel],
720823
// });
721824
// }
825+
- name: Apply beta-blocker title label
826+
uses: actions/github-script@v8
827+
with:
828+
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
829+
script: |
830+
const issue = context.payload.issue;
831+
if (!issue || issue.pull_request) {
832+
return;
833+
}
834+
835+
const labelName = "beta-blocker";
836+
const matchesBetaBlocker = /^beta blocker:/i.test(issue.title ?? "");
837+
838+
try {
839+
await github.rest.issues.getLabel({
840+
owner: context.repo.owner,
841+
repo: context.repo.repo,
842+
name: labelName,
843+
});
844+
} catch (error) {
845+
if (error?.status !== 404) {
846+
throw error;
847+
}
848+
core.info(`Skipping ${labelName} labeling because the label does not exist in the repository.`);
849+
return;
850+
}
851+
852+
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
853+
owner: context.repo.owner,
854+
repo: context.repo.repo,
855+
issue_number: issue.number,
856+
per_page: 100,
857+
});
858+
const hasLabel = currentLabels.some((label) => label.name === labelName);
859+
860+
if (matchesBetaBlocker && !hasLabel) {
861+
await github.rest.issues.addLabels({
862+
owner: context.repo.owner,
863+
repo: context.repo.repo,
864+
issue_number: issue.number,
865+
labels: [labelName],
866+
});
867+
return;
868+
}
869+
870+
if (!matchesBetaBlocker && hasLabel) {
871+
await github.rest.issues.removeLabel({
872+
owner: context.repo.owner,
873+
repo: context.repo.repo,
874+
issue_number: issue.number,
875+
name: labelName,
876+
});
877+
}

docs/plugins/building-plugins.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,9 @@ internal imports — never import your own plugin through its SDK path.
229229
1. Watch for GitHub release tags on [openclaw/openclaw](https://github.com/openclaw/openclaw/releases) and subscribe via `Watch` > `Releases`. Beta tags look like `v2026.3.N-beta.1`. You can also turn on notifications for the official OpenClaw X account [@openclaw](https://x.com/openclaw) for release announcements.
230230
2. Test your plugin against the beta tag as soon as it appears. The window before stable is typically only a few hours.
231231
3. Post in your plugin's thread in the `plugin-forum` Discord channel after testing with either `all good` or what broke. If you do not have a thread yet, create one.
232-
4. If something breaks, ship a fix PR to `main` and drop the link in your thread. Blockers with a PR get merged; blockers without one might ship anyway. Maintainers watch these threads during beta testing.
233-
5. Silence means green. If you miss the window, your fix likely lands in the next cycle.
232+
4. If something breaks, open or update an issue titled `Beta blocker: <plugin-name> - <summary>` and apply the `beta-blocker` label. Put the issue link in your thread.
233+
5. Open a PR to `main` titled `fix(<plugin-id>): beta blocker - <summary>` and link the issue in both the PR and your Discord thread. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation. Blockers with a PR get merged; blockers without one might ship anyway. Maintainers watch these threads during beta testing.
234+
6. Silence means green. If you miss the window, your fix likely lands in the next cycle.
234235

235236
## Next steps
236237

scripts/sync-labels.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { resolve } from "node:path";
55
type RepoLabel = {
66
name: string;
77
color?: string;
8+
description?: string;
89
};
910

1011
const COLOR_BY_PREFIX = new Map<string, string>([
@@ -17,8 +18,31 @@ const COLOR_BY_PREFIX = new Map<string, string>([
1718
["size", "fbca04"],
1819
]);
1920

21+
const EXTRA_LABEL_METADATA = new Map<
22+
string,
23+
{
24+
color: string;
25+
description?: string;
26+
}
27+
>([
28+
[
29+
"beta-blocker",
30+
{
31+
color: "D93F0B",
32+
description: "Plugin beta-release blocker pending stable cutoff triage",
33+
},
34+
],
35+
]);
36+
2037
const configPath = resolve(".github/labeler.yml");
21-
const EXTRA_LABELS = ["size: XS", "size: S", "size: M", "size: L", "size: XL"] as const;
38+
const EXTRA_LABELS = [
39+
"size: XS",
40+
"size: S",
41+
"size: M",
42+
"size: L",
43+
"size: XL",
44+
"beta-blocker",
45+
] as const;
2246
const labelNames = [
2347
...new Set([...extractLabelNames(readFileSync(configPath, "utf8")), ...EXTRA_LABELS]),
2448
];
@@ -37,12 +61,21 @@ if (!missing.length) {
3761
}
3862

3963
for (const label of missing) {
40-
const color = pickColor(label);
41-
execFileSync(
42-
"gh",
43-
["api", "-X", "POST", `repos/${repo}/labels`, "-f", `name=${label}`, "-f", `color=${color}`],
44-
{ stdio: "inherit" },
45-
);
64+
const metadata = resolveLabelMetadata(label);
65+
const args = [
66+
"api",
67+
"-X",
68+
"POST",
69+
`repos/${repo}/labels`,
70+
"-f",
71+
`name=${label}`,
72+
"-f",
73+
`color=${metadata.color}`,
74+
];
75+
if (metadata.description) {
76+
args.push("-f", `description=${metadata.description}`);
77+
}
78+
execFileSync("gh", args, { stdio: "inherit" });
4679
console.log(`Created label: ${label}`);
4780
}
4881

@@ -66,9 +99,13 @@ function extractLabelNames(contents: string): string[] {
6699
return labels;
67100
}
68101

69-
function pickColor(label: string): string {
102+
function resolveLabelMetadata(label: string): { color: string; description?: string } {
103+
const extraMetadata = EXTRA_LABEL_METADATA.get(label);
104+
if (extraMetadata) {
105+
return extraMetadata;
106+
}
70107
const prefix = label.includes(":") ? label.split(":", 1)[0].trim() : label.trim();
71-
return COLOR_BY_PREFIX.get(prefix) ?? "ededed";
108+
return { color: COLOR_BY_PREFIX.get(prefix) ?? "ededed" };
72109
}
73110

74111
function resolveRepo(): string {

src/config/io.owner-display-secret.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ async function waitForPersistedSecret(configPath: string, expectedSecret: string
99
const deadline = Date.now() + 3_000;
1010
while (Date.now() < deadline) {
1111
const raw = await fs.readFile(configPath, "utf-8");
12-
const parsed = JSON.parse(raw) as {
12+
let parsed: {
1313
commands?: { ownerDisplaySecret?: string };
1414
};
15+
try {
16+
parsed = JSON.parse(raw) as {
17+
commands?: { ownerDisplaySecret?: string };
18+
};
19+
} catch {
20+
await sleep(5);
21+
continue;
22+
}
1523
if (parsed.commands?.ownerDisplaySecret === expectedSecret) {
1624
return;
1725
}

0 commit comments

Comments
 (0)