Compare commits
5 Commits
main
...
ops/github
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d41346b090 | ||
|
|
f235dafeb1 | ||
|
|
cf213a8d7d | ||
|
|
c9abff105d | ||
|
|
e94a689db5 |
60
.github/workflows/cluster-worker.yml
vendored
60
.github/workflows/cluster-worker.yml
vendored
@ -73,6 +73,8 @@ jobs:
|
||||
CLOWNFISH_MODEL: ${{ inputs.model || vars.CLOWNFISH_MODEL || 'gpt-5.5' }}
|
||||
CODEX_CLI_VERSION: ${{ vars.CLOWNFISH_CODEX_CLI_VERSION || '0.125.0' }}
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
CLOWNFISH_APP_ID: ${{ vars.CLOWNFISH_APP_ID || secrets.CLOWNFISH_APP_ID }}
|
||||
CLOWNFISH_APP_AUTH_ENABLED: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY != '' && (vars.CLOWNFISH_APP_ID != '' || secrets.CLOWNFISH_APP_ID != '') && '1' || '0' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
@ -99,12 +101,27 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node24-codex-
|
||||
|
||||
- name: Create GitHub App read token
|
||||
id: read_app_token
|
||||
if: ${{ env.CLOWNFISH_APP_AUTH_ENABLED == '1' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ env.CLOWNFISH_APP_ID }}
|
||||
private-key: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY }}
|
||||
owner: openclaw
|
||||
repositories: |
|
||||
openclaw
|
||||
clownfish
|
||||
permission-contents: read
|
||||
permission-issues: read
|
||||
permission-pull-requests: read
|
||||
|
||||
- name: Verify GitHub read token
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_READ_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.read_app_token.outputs.token || secrets.CLOWNFISH_READ_GH_TOKEN }}
|
||||
run: |
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
echo "CLOWNFISH_READ_GH_TOKEN is required"
|
||||
echo "CLOWNFISH_APP_ID + CLOWNFISH_APP_PRIVATE_KEY or CLOWNFISH_READ_GH_TOKEN is required"
|
||||
exit 1
|
||||
fi
|
||||
gh auth status
|
||||
@ -133,7 +150,7 @@ jobs:
|
||||
|
||||
- name: Run worker
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_READ_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.read_app_token.outputs.token || secrets.CLOWNFISH_READ_GH_TOKEN }}
|
||||
run: |
|
||||
args=("${{ inputs.job }}" --mode "${{ inputs.mode }}")
|
||||
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
||||
@ -193,6 +210,8 @@ jobs:
|
||||
CLOWNFISH_GIT_USER_EMAIL: ${{ vars.CLOWNFISH_GIT_USER_EMAIL || 'projectclownfish@users.noreply.github.com' }}
|
||||
CODEX_CLI_VERSION: ${{ vars.CLOWNFISH_CODEX_CLI_VERSION || '0.125.0' }}
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
CLOWNFISH_APP_ID: ${{ vars.CLOWNFISH_APP_ID || secrets.CLOWNFISH_APP_ID }}
|
||||
CLOWNFISH_APP_AUTH_ENABLED: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY != '' && (vars.CLOWNFISH_APP_ID != '' || secrets.CLOWNFISH_APP_ID != '') && '1' || '0' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
@ -213,6 +232,31 @@ jobs:
|
||||
${{ runner.os }}-node24-codex-${{ env.CODEX_CLI_VERSION }}-target-pnpm-
|
||||
${{ runner.os }}-node24-codex-
|
||||
|
||||
- name: Create GitHub App write token
|
||||
id: write_app_token
|
||||
if: ${{ env.CLOWNFISH_APP_AUTH_ENABLED == '1' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ env.CLOWNFISH_APP_ID }}
|
||||
private-key: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY }}
|
||||
owner: openclaw
|
||||
repositories: |
|
||||
openclaw
|
||||
clownfish
|
||||
permission-contents: write
|
||||
permission-issues: write
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Verify GitHub write token
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.write_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN }}
|
||||
run: |
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
echo "CLOWNFISH_APP_ID + CLOWNFISH_APP_PRIVATE_KEY or CLOWNFISH_GH_TOKEN is required"
|
||||
exit 1
|
||||
fi
|
||||
gh auth status
|
||||
|
||||
- name: Install Codex CLI
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@ -245,31 +289,31 @@ jobs:
|
||||
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' && env.CLOWNFISH_ALLOW_FIX_PR == '1' }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.write_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN }}
|
||||
run: npm run execute-fix -- "${{ inputs.job }}" --latest
|
||||
|
||||
- name: Apply safe closure actions
|
||||
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.write_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN }}
|
||||
run: npm run apply-result -- "${{ inputs.job }}" --latest
|
||||
|
||||
- name: Post-flight finalize fix PRs
|
||||
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' && env.CLOWNFISH_ALLOW_FIX_PR == '1' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.write_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN }}
|
||||
run: npm run post-flight -- "${{ inputs.job }}" --latest
|
||||
|
||||
- name: Apply post-flight closeouts
|
||||
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.write_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN }}
|
||||
run: npm run apply-result -- "${{ inputs.job }}" --latest
|
||||
|
||||
- name: Tag Clownfish targets
|
||||
if: ${{ always() && env.CLOWNFISH_ALLOW_EXECUTE == '1' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.write_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN }}
|
||||
run: npm run tag-clownfish -- .projectclownfish/runs --apply --live --open-branches false --report .projectclownfish/runs/clownfish-label-report.json
|
||||
|
||||
- name: Upload final worker artifacts
|
||||
|
||||
19
.github/workflows/publish-results.yml
vendored
19
.github/workflows/publish-results.yml
vendored
@ -14,6 +14,8 @@ permissions:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
CLOWNFISH_APP_ID: ${{ vars.CLOWNFISH_APP_ID || secrets.CLOWNFISH_APP_ID }}
|
||||
CLOWNFISH_APP_AUTH_ENABLED: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY != '' && (vars.CLOWNFISH_APP_ID != '' || secrets.CLOWNFISH_APP_ID != '') && '1' || '0' }}
|
||||
|
||||
concurrency:
|
||||
group: projectclownfish-publish-results
|
||||
@ -32,6 +34,21 @@ jobs:
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Create GitHub App read token
|
||||
id: read_app_token
|
||||
if: ${{ env.CLOWNFISH_APP_AUTH_ENABLED == '1' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ env.CLOWNFISH_APP_ID }}
|
||||
private-key: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY }}
|
||||
owner: openclaw
|
||||
repositories: |
|
||||
openclaw
|
||||
clownfish
|
||||
permission-contents: read
|
||||
permission-issues: read
|
||||
permission-pull-requests: read
|
||||
|
||||
- name: Download worker artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@ -50,7 +67,7 @@ jobs:
|
||||
|
||||
- name: Publish and commit result ledger
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CLOWNFISH_READ_GH_TOKEN || github.token }}
|
||||
GH_TOKEN: ${{ steps.read_app_token.outputs.token || secrets.CLOWNFISH_READ_GH_TOKEN || github.token }}
|
||||
RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
RUN_URL: ${{ github.event.workflow_run.html_url }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
|
||||
10
README.md
10
README.md
@ -224,7 +224,7 @@ Each cluster job:
|
||||
8. Applies guarded close/comment and explicit merge actions through `scripts/apply-result.mjs`.
|
||||
9. Publishes a sanitized result ledger back to this repo under `results/`, `jobs/openclaw/closed/`, `apply-report.json`, and this README dashboard.
|
||||
|
||||
Codex does not receive a GitHub token during classification. The runner preflights GitHub state before model execution, then Codex receives those artifacts and returns JSON only. When a reviewed fix artifact is executed, Codex gets a temporary target checkout without GitHub credentials; the deterministic executor owns commit, push, PR creation, and source-PR closeout using `CLOWNFISH_GH_TOKEN`. Commit author metadata defaults to `projectclownfish` and can be overridden with `CLOWNFISH_GIT_USER_NAME` and `CLOWNFISH_GIT_USER_EMAIL`; this is separate from the GitHub token used to push. The applicator re-fetches the target item, checks `updated_at`, blocks unsafe closeouts, writes idempotent close comments, closes supported duplicate/superseded/fixed-by-candidate actions, and can squash-merge explicitly allowed clean PR actions.
|
||||
Codex does not receive a GitHub token during classification. The runner preflights GitHub state before model execution, then Codex receives those artifacts and returns JSON only. The preferred GitHub auth path is a short-lived installation token minted from `CLOWNFISH_APP_ID` and `CLOWNFISH_APP_PRIVATE_KEY`; legacy `CLOWNFISH_READ_GH_TOKEN` and `CLOWNFISH_GH_TOKEN` secrets remain fallbacks. The read token is narrowed to read-only issue, PR, content, checks, and status access. When a reviewed fix artifact is executed, Codex gets a temporary target checkout without GitHub credentials; the deterministic executor owns commit, push, PR creation, and source-PR closeout using a write-scoped GitHub App token or `CLOWNFISH_GH_TOKEN`. Commit author metadata defaults to `projectclownfish` and can be overridden with `CLOWNFISH_GIT_USER_NAME` and `CLOWNFISH_GIT_USER_EMAIL`; this is separate from the GitHub token used to push. The applicator re-fetches the target item, checks `updated_at`, blocks unsafe closeouts, writes idempotent close comments, closes supported duplicate/superseded/fixed-by-candidate actions, and can squash-merge explicitly allowed clean PR actions.
|
||||
|
||||
Merge is deliberately harder than closeout. A merge action must include `merge_preflight` proving security clearance, resolved human comments, resolved review-bot findings, a passed Codex `/review`, addressed review findings, and clean validation commands. The fix executor runs an agentic edit/review loop before it writes a fix PR: edit, validate, Codex `/review`, address findings, revalidate, and resolve PR review threads when permitted. The applicator also checks live unresolved GitHub review threads immediately before merge.
|
||||
|
||||
@ -234,6 +234,10 @@ Runs for the same job path and mode are queued instead of running concurrently.
|
||||
|
||||
Full worker prompts, Codex transcripts, and raw artifacts stay in GitHub Actions. The committed ledger keeps only the cluster summary, run URL, action counts, apply outcomes, closed targets, and needs-human entries.
|
||||
|
||||
## GitHub App Auth
|
||||
|
||||
Create a GitHub App installed on `openclaw/openclaw` and `openclaw/clownfish`. Give it `Contents: write`, `Issues: write`, and `Pull requests: write`; leave webhooks disabled. Store the App ID as repository variable `CLOWNFISH_APP_ID` and the downloaded private key PEM as repository secret `CLOWNFISH_APP_PRIVATE_KEY` in `openclaw/clownfish`. The workflows mint per-job tokens with the minimum permission level needed for that job, so classification stays read-only and execution gets write access only after the execution gate opens. Merge remains disabled unless `CLOWNFISH_ALLOW_MERGE=1`.
|
||||
|
||||
## Modes
|
||||
|
||||
- `plan`: produces recommendations only.
|
||||
@ -242,7 +246,7 @@ Full worker prompts, Codex transcripts, and raw artifacts stay in GitHub Actions
|
||||
- `route_security`: quarantines true security-sensitive refs without poisoning unrelated cluster work.
|
||||
- `needs_human`: only product-direction, trust-boundary, canonical-choice, merge-path, or contributor-credit decisions that remain unclear after the hydrated artifact and single-item review/check/decide pass.
|
||||
- Automated reviewer feedback must be cleared during autonomous PR work. Greptile, Codex, Asile, CodeRabbit, Copilot, and similar bot comments must be addressed, proven non-actionable, or escalated before any merge or post-merge closeout recommendation.
|
||||
- Merge preflight: no PR can merge until `CLOWNFISH_ALLOW_MERGE=1`, security issues are cleared, comments are resolved, Codex `/review` has passed, findings are addressed, and changed-surface validation is clean. With the merge gate closed, ProjectClownfish labels merge-ready targets for human review instead of merging.
|
||||
- Merge preflight: no PR can merge until `CLOWNFISH_ALLOW_MERGE=1`, security issues are cleared, comments are resolved, Codex `/review` has passed, findings are addressed, and changed-surface validation is clean. With the merge gate closed, ProjectClownfish applies the single `clownfish` label and leaves the final merge to a maintainer.
|
||||
- Repair ladder: make the useful contributor PR mergeable when its branch is maintainer-editable; otherwise replace draft, stale, unmergeable, uneditable, or unsafe branches with a narrow credited fix PR. When fix PR mode is enabled, "wait or replace" is already answered: replace, preserve credit, then supersede only the source PR that could not be safely updated.
|
||||
|
||||
## Local Run
|
||||
@ -349,7 +353,7 @@ The workflow needs:
|
||||
- a read-only GitHub token for worker inspection
|
||||
- a separate write-scoped GitHub token for the deterministic applicator
|
||||
- execution gates that default on for execute/autonomous jobs: set `CLOWNFISH_ALLOW_EXECUTE=0` or `CLOWNFISH_ALLOW_FIX_PR=0` only when intentionally pausing live work
|
||||
- merge is separately gated by `CLOWNFISH_ALLOW_MERGE`; it defaults to `0`, and merge-ready PRs are labeled `clownfish:human-review` and `clownfish:merge-ready` for a maintainer to merge manually
|
||||
- merge is separately gated by `CLOWNFISH_ALLOW_MERGE`; it defaults to `0`, and merge-ready PRs keep only the orange `clownfish` label for a maintainer to merge manually
|
||||
- optional `CLOWNFISH_CODEX_CLI_VERSION` variable to pin and refresh the cached Codex CLI
|
||||
- optional `CLOWNFISH_MODEL` override for dispatch scripts; default Codex model is `gpt-5.5`
|
||||
- optional `CLOWNFISH_MAX_LIVE_WORKERS` variable for dispatch/requeue/self-heal worker fan-out; default is `50`
|
||||
|
||||
@ -18,8 +18,9 @@ const MERGE_ACTIONS = new Set(["merge_candidate", "merge_canonical"]);
|
||||
const CLOSE_CLASSIFICATIONS = new Set(["duplicate", "superseded", "fixed_by_candidate", "low_signal"]);
|
||||
const PASSING_CHECK_CONCLUSIONS = new Set(["SUCCESS", "SKIPPED", "NEUTRAL"]);
|
||||
const CLEAN_MERGE_STATES = new Set(["CLEAN"]);
|
||||
const HUMAN_REVIEW_LABEL = "clownfish:human-review";
|
||||
const MERGE_READY_LABEL = "clownfish:merge-ready";
|
||||
const CLOWNFISH_LABEL = "clownfish";
|
||||
const CLOWNFISH_LABEL_COLOR = "F97316";
|
||||
const CLOWNFISH_LABEL_DESCRIPTION = "Tracked by Clownfish automation";
|
||||
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const jobPath = args._[0];
|
||||
@ -406,11 +407,11 @@ function applyMergeAction({ job, result, action, dryRun, allowMissingUpdatedAt,
|
||||
}
|
||||
|
||||
if (process.env.CLOWNFISH_ALLOW_MERGE !== "1") {
|
||||
if (!dryRun) labelForHumanMergeReview(result.repo, target);
|
||||
if (!dryRun) labelForClownfishReview(result.repo, target);
|
||||
return {
|
||||
...base,
|
||||
status: "blocked",
|
||||
reason: "merge requires CLOWNFISH_ALLOW_MERGE=1; labeled for human review",
|
||||
reason: "merge requires CLOWNFISH_ALLOW_MERGE=1; labeled clownfish",
|
||||
live_state: live.state,
|
||||
live_updated_at: live.updated_at,
|
||||
merge_method: "squash",
|
||||
@ -494,11 +495,9 @@ function validateMergePolicy({ job, action }) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function labelForHumanMergeReview(repo, target) {
|
||||
ensureLabel(repo, HUMAN_REVIEW_LABEL, "B60205", "Needs maintainer review before ProjectClownfish can finish");
|
||||
ensureLabel(repo, MERGE_READY_LABEL, "0E8A16", "ProjectClownfish found a merge-ready candidate; human owns the final merge");
|
||||
ghBestEffort(["issue", "edit", String(target), "--repo", repo, "--add-label", HUMAN_REVIEW_LABEL]);
|
||||
ghBestEffort(["issue", "edit", String(target), "--repo", repo, "--add-label", MERGE_READY_LABEL]);
|
||||
function labelForClownfishReview(repo, target) {
|
||||
ensureLabel(repo, CLOWNFISH_LABEL, CLOWNFISH_LABEL_COLOR, CLOWNFISH_LABEL_DESCRIPTION);
|
||||
ghBestEffort(["issue", "edit", String(target), "--repo", repo, "--add-label", CLOWNFISH_LABEL]);
|
||||
}
|
||||
|
||||
function ensureLabel(repo, name, color, description) {
|
||||
|
||||
@ -11,8 +11,9 @@ const FIX_PR_ACTIONS = new Set(["open_fix_pr", "repair_contributor_branch"]);
|
||||
const FIX_PR_READY_STATUSES = new Set(["opened", "pushed"]);
|
||||
const POST_MERGE_CLOSE_ACTIONS = new Set(["close_duplicate", "close_superseded", "close_fixed_by_candidate", "post_merge_close"]);
|
||||
const DEFAULT_IGNORED_CHECKS = ["auto-response", "Labeler", "Stale"];
|
||||
const HUMAN_REVIEW_LABEL = "clownfish:human-review";
|
||||
const MERGE_READY_LABEL = "clownfish:merge-ready";
|
||||
const CLOWNFISH_LABEL = "clownfish";
|
||||
const CLOWNFISH_LABEL_COLOR = "F97316";
|
||||
const CLOWNFISH_LABEL_DESCRIPTION = "Tracked by Clownfish automation";
|
||||
const POST_FLIGHT_WAIT_MS = numberEnv("CLOWNFISH_POST_FLIGHT_WAIT_MS", 10 * 60 * 1000);
|
||||
const POST_FLIGHT_POLL_MS = numberEnv("CLOWNFISH_POST_FLIGHT_POLL_MS", 15 * 1000);
|
||||
|
||||
@ -161,11 +162,11 @@ function finalizeFixPr(action) {
|
||||
}
|
||||
|
||||
if (process.env.CLOWNFISH_ALLOW_MERGE !== "1") {
|
||||
labelForHumanMergeReview(result.repo, parsed.number);
|
||||
labelForClownfishReview(result.repo, parsed.number);
|
||||
return {
|
||||
...prBase,
|
||||
status: "blocked",
|
||||
reason: "merge requires CLOWNFISH_ALLOW_MERGE=1; labeled for human review",
|
||||
reason: "merge requires CLOWNFISH_ALLOW_MERGE=1; labeled clownfish",
|
||||
merge_method: "squash",
|
||||
waited_ms: waitedMs,
|
||||
};
|
||||
@ -288,11 +289,9 @@ function validateMergePolicy() {
|
||||
return "";
|
||||
}
|
||||
|
||||
function labelForHumanMergeReview(repo, number) {
|
||||
ensureLabel(repo, HUMAN_REVIEW_LABEL, "B60205", "Needs maintainer review before ProjectClownfish can finish");
|
||||
ensureLabel(repo, MERGE_READY_LABEL, "0E8A16", "ProjectClownfish found a merge-ready candidate; human owns the final merge");
|
||||
ghBestEffort(["issue", "edit", String(number), "--repo", repo, "--add-label", HUMAN_REVIEW_LABEL]);
|
||||
ghBestEffort(["issue", "edit", String(number), "--repo", repo, "--add-label", MERGE_READY_LABEL]);
|
||||
function labelForClownfishReview(repo, number) {
|
||||
ensureLabel(repo, CLOWNFISH_LABEL, CLOWNFISH_LABEL_COLOR, CLOWNFISH_LABEL_DESCRIPTION);
|
||||
ghBestEffort(["issue", "edit", String(number), "--repo", repo, "--add-label", CLOWNFISH_LABEL]);
|
||||
}
|
||||
|
||||
function ensureLabel(repo, name, color, description) {
|
||||
|
||||
@ -5,6 +5,8 @@ import { execFileSync } from "node:child_process";
|
||||
import { currentProjectRepo, parseArgs, parseSimpleYaml, repoRoot } from "./lib.mjs";
|
||||
|
||||
const DEFAULT_LABEL = "clownfish";
|
||||
const DEFAULT_LABEL_COLOR = "F97316";
|
||||
const DEFAULT_LABEL_DESCRIPTION = "Tracked by Clownfish automation";
|
||||
const FIX_PR_STATUSES = new Set(["opened", "pushed", "executed", "blocked", "planned"]);
|
||||
const APPLY_STATUSES = new Set(["executed"]);
|
||||
const CLOSE_ACTIONS = new Set([
|
||||
@ -327,7 +329,7 @@ function createGithubLabel() {
|
||||
const repo = process.env.CLOWNFISH_TARGET_REPO ?? "openclaw/openclaw";
|
||||
execFileSync(
|
||||
"gh",
|
||||
["label", "create", labelName, "--repo", repo, "--color", "0E8A16", "--description", "Tracked by Clownfish automation"],
|
||||
["label", "create", labelName, "--repo", repo, "--color", DEFAULT_LABEL_COLOR, "--description", DEFAULT_LABEL_DESCRIPTION],
|
||||
{ cwd: repoRoot(), encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "pipe"] },
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user