Skip to content

Commit 5bfb353

Browse files
authored
Merge branch 'main' into feat/google-vertex-provider
2 parents ace1ec7 + 5f89cab commit 5bfb353

661 files changed

Lines changed: 25307 additions & 5377 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
name: openclaw-release-ci
3+
description: "Run, watch, debug, and summarize OpenClaw full release CI, release checks, live provider gates, install/update proofs, and release-secret preflights."
4+
---
5+
6+
# OpenClaw Release CI
7+
8+
Use this with `$openclaw-release-maintainer` and `$openclaw-testing` when a release candidate needs full validation, install/update proof, live provider checks, or CI recovery.
9+
10+
## Guardrails
11+
12+
- No version bump, tag, npm publish, GitHub release, or release promotion without explicit operator approval.
13+
- Validate provider secrets before dispatching expensive full release matrices.
14+
- Do not set GitHub secrets from unvalidated 1Password candidates. If a candidate returns 401/403, leave the existing secret alone and report the exact missing provider.
15+
- Use `$one-password` for secret reads/writes: one persistent tmux session, targeted items only, no secret output.
16+
- Watch one parent run plus compact child summaries. Avoid broad `gh run view` polling loops; REST quota is easy to burn.
17+
- Fetch logs only for failed or currently-blocking jobs. If quota is low, stop polling and wait for reset.
18+
- Treat live-provider flakes separately from code failures: prove key validity, provider HTTP status, retry evidence, and exact failing lane before editing code.
19+
20+
## Preflight
21+
22+
Before full release validation:
23+
24+
```bash
25+
node .agents/skills/openclaw-release-ci/scripts/verify-provider-secrets.mjs --required openai,anthropic,fireworks
26+
gh api rate_limit --jq '.resources.core'
27+
git status --short --branch
28+
git rev-parse HEAD
29+
```
30+
31+
If env lacks keys, use `$one-password` to inject or set them, then rerun the script. The script prints only provider status and HTTP class, never tokens.
32+
33+
## Dispatch
34+
35+
Prefer the trusted workflow on `main`, target the exact release SHA:
36+
37+
```bash
38+
gh workflow run full-release-validation.yml \
39+
--repo openclaw/openclaw \
40+
--ref main \
41+
-f ref=<release-sha> \
42+
-f provider=openai \
43+
-f mode=both \
44+
-f release_profile=full \
45+
-f rerun_group=all
46+
```
47+
48+
Use `release_profile=stable` unless the operator explicitly asks for the broad advisory provider/media matrix. Use narrow `rerun_group` after focused fixes.
49+
50+
## Watch
51+
52+
Use the summary helper instead of repeated raw polling:
53+
54+
```bash
55+
node .agents/skills/openclaw-release-ci/scripts/release-ci-summary.mjs <full-release-run-id>
56+
```
57+
58+
Then watch only when useful:
59+
60+
```bash
61+
gh run watch <full-release-run-id> --repo openclaw/openclaw --exit-status
62+
```
63+
64+
Stop watchers before ending the turn or switching strategy.
65+
66+
## Failure Triage
67+
68+
1. Confirm parent SHA and child run IDs.
69+
2. List failed jobs only:
70+
```bash
71+
gh run view <child-run-id> --repo openclaw/openclaw --json jobs \
72+
--jq '.jobs[] | select(.conclusion=="failure" or .conclusion=="timed_out" or .conclusion=="cancelled") | [.databaseId,.name,.conclusion,.url] | @tsv'
73+
```
74+
3. Fetch one failed job log. If rate-limited, note reset time and avoid more REST calls.
75+
4. For secret-looking failures, validate the provider endpoint from the same secret source before editing code.
76+
5. For live-cache failures, inspect whether it is missing/invalid key, empty text, provider refusal, timeout, or baseline miss. Do not weaken release gates without clear provider evidence.
77+
6. Fix narrowly, run local/changed proof, commit, push, rerun the smallest matching group.
78+
79+
## Evidence
80+
81+
Record:
82+
83+
- release SHA
84+
- full parent run URL
85+
- child run IDs and conclusions: CI, Release Checks, Plugin Prerelease, NPM Telegram
86+
- targeted local proof commands
87+
- provider-secret preflight result
88+
- known gaps or unrelated failures
89+
90+
For lessons and recovery patterns, read `references/release-ci-notes.md`.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface:
2+
display_name: "OpenClaw Release CI"
3+
short_description: "Verify and debug OpenClaw release validation runs"
4+
default_prompt: "Use $openclaw-release-ci to preflight provider secrets, watch full release validation, summarize child runs, and triage only failing release lanes."
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Release CI Notes
2+
3+
## What Went Wrong
4+
5+
- Full validation was started before all provider keys were proven valid.
6+
- GitHub secret presence was confused with key validity.
7+
- Repeated `gh run view` and log fetches exhausted REST quota.
8+
- Parent run state was less useful than child run evidence.
9+
- Live-cache failures needed structured classification: invalid key, empty provider output, timeout, or real cache regression.
10+
- Background watchers accumulated and made interruption recovery harder.
11+
12+
## Better Defaults
13+
14+
- Run provider-secret preflight first. Require real `/models` or equivalent endpoint checks for release-blocking providers.
15+
- Keep one watcher open. Use child summaries every few minutes, not every few seconds.
16+
- Fetch failed-job logs only after a job reaches a terminal failing state.
17+
- Prefer narrow `rerun_group` recovery after a focused fix.
18+
- Leave bad secrets unset. A 401 candidate from 1Password should not overwrite GitHub.
19+
- Make the final release evidence note durable: parent URL, child run URLs, SHA, command proof, and gaps.
20+
21+
## Secret Handling Pattern
22+
23+
- Use `$one-password`; never run broad env dumps.
24+
- Search exact item titles or known ids.
25+
- Validate candidates without printing values.
26+
- Set GitHub secrets only after endpoint validation succeeds.
27+
- After setting, verify metadata with `gh secret list`, not value output.
28+
29+
## Live Cache Pattern
30+
31+
- Empty text with token usage is a provider/output issue until proven otherwise.
32+
- Retry lane-level mismatches once with a fresh session id.
33+
- Keep cache baselines strict, but log enough structured usage to distinguish cache miss from response mismatch.
34+
- If a provider key validates locally but fails in Actions, inspect whether the workflow reads the expected secret name.
35+
36+
## Quota-Safe GitHub Pattern
37+
38+
- Check `gh api rate_limit --jq '.resources.core'` before log-heavy work.
39+
- Use one child-run listing call, then inspect failed jobs only.
40+
- If remaining quota is low, pause until reset; do not keep polling.
41+
- Prefer GraphQL only for metadata when REST is exhausted; logs still need REST.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env node
2+
import { execFileSync } from "node:child_process";
3+
import process from "node:process";
4+
5+
const runId = process.argv[2];
6+
const repo = process.env.OPENCLAW_RELEASE_REPO || "openclaw/openclaw";
7+
8+
if (!runId) {
9+
console.error("usage: release-ci-summary.mjs <full-release-run-id>");
10+
process.exit(2);
11+
}
12+
13+
function gh(args) {
14+
return execFileSync("gh", args, {
15+
encoding: "utf8",
16+
stdio: ["ignore", "pipe", "pipe"],
17+
});
18+
}
19+
20+
function jsonGh(args) {
21+
return JSON.parse(gh(args));
22+
}
23+
24+
function rate() {
25+
try {
26+
return jsonGh(["api", "rate_limit"]).resources.core;
27+
} catch {
28+
return undefined;
29+
}
30+
}
31+
32+
const core = rate();
33+
if (core) {
34+
const reset = new Date(core.reset * 1000).toISOString();
35+
console.log(`rate: remaining=${core.remaining}/${core.limit} reset=${reset}`);
36+
if (core.remaining < 20) {
37+
console.error("rate too low for CI summary; wait for reset before polling");
38+
process.exit(3);
39+
}
40+
}
41+
42+
const parent = jsonGh([
43+
"run",
44+
"view",
45+
runId,
46+
"--repo",
47+
repo,
48+
"--json",
49+
"status,conclusion,createdAt,headSha,url,jobs",
50+
]);
51+
52+
console.log(`parent: ${runId} ${parent.status}/${parent.conclusion || "none"}`);
53+
console.log(`sha: ${parent.headSha}`);
54+
console.log(`url: ${parent.url}`);
55+
56+
for (const job of parent.jobs ?? []) {
57+
const marker = job.conclusion || job.status;
58+
console.log(`parent-job: ${marker} ${job.name}`);
59+
}
60+
61+
const since = parent.createdAt;
62+
const runList = gh([
63+
"api",
64+
`repos/${repo}/actions/runs?per_page=100`,
65+
"--jq",
66+
`.workflow_runs[] | select(.created_at >= "${since}") | select(.name=="CI" or .name=="OpenClaw Release Checks" or .name=="Plugin Prerelease" or .name=="NPM Telegram Beta E2E" or .name=="Full Release Validation") | [.id,.name,.status,.conclusion,.head_sha,.html_url] | @tsv`,
67+
]).trim();
68+
69+
if (!runList) {
70+
console.log("children: none found yet");
71+
process.exit(0);
72+
}
73+
74+
console.log("children:");
75+
for (const line of runList.split("\n")) {
76+
const [id, name, status, conclusion, sha, url] = line.split("\t");
77+
console.log(`child: ${id} ${name} ${status}/${conclusion || "none"} sha=${sha}`);
78+
console.log(`child-url: ${url}`);
79+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env node
2+
import process from "node:process";
3+
4+
const args = new Map();
5+
for (let index = 2; index < process.argv.length; index += 1) {
6+
const arg = process.argv[index];
7+
if (!arg.startsWith("--")) continue;
8+
const [key, inlineValue] = arg.slice(2).split("=", 2);
9+
const value = inlineValue ?? process.argv[index + 1];
10+
if (inlineValue === undefined) index += 1;
11+
args.set(key, value);
12+
}
13+
14+
const requiredInput = String(args.get("required") ?? "openai,anthropic").trim();
15+
const required = new Set(
16+
(requiredInput.toLowerCase() === "none" ? "" : requiredInput)
17+
.split(",")
18+
.map((entry) => entry.trim().toLowerCase())
19+
.filter(Boolean),
20+
);
21+
22+
const timeoutMs = Number(args.get("timeout-ms") ?? 10_000);
23+
24+
function envFirst(names) {
25+
for (const name of names) {
26+
const value = process.env[name]?.trim();
27+
if (value) return { name, value };
28+
}
29+
return undefined;
30+
}
31+
32+
async function checkProvider(id, config) {
33+
const secret = envFirst(config.env);
34+
if (!secret) {
35+
return { id, ok: false, status: "missing", env: config.env.join("|") };
36+
}
37+
38+
const controller = new AbortController();
39+
const timer = setTimeout(() => controller.abort(), timeoutMs);
40+
try {
41+
const headers = config.headers(secret.value);
42+
const response = await fetch(config.url, {
43+
headers,
44+
signal: controller.signal,
45+
});
46+
return {
47+
id,
48+
ok: response.ok,
49+
status: response.ok ? "ok" : `http_${response.status}`,
50+
env: secret.name,
51+
};
52+
} catch (error) {
53+
return {
54+
id,
55+
ok: false,
56+
status: error?.name === "AbortError" ? "timeout" : "error",
57+
env: secret.name,
58+
};
59+
} finally {
60+
clearTimeout(timer);
61+
}
62+
}
63+
64+
const providers = {
65+
openai: {
66+
env: ["OPENAI_API_KEY"],
67+
url: "https://api.openai.com/v1/models",
68+
headers: (token) => ({ authorization: `Bearer ${token}` }),
69+
},
70+
anthropic: {
71+
env: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_TOKEN"],
72+
url: "https://api.anthropic.com/v1/models",
73+
headers: (token) => ({
74+
"anthropic-version": "2023-06-01",
75+
"x-api-key": token,
76+
}),
77+
},
78+
fireworks: {
79+
env: ["FIREWORKS_API_KEY"],
80+
url: "https://api.fireworks.ai/inference/v1/models",
81+
headers: (token) => ({ authorization: `Bearer ${token}` }),
82+
},
83+
openrouter: {
84+
env: ["OPENROUTER_API_KEY"],
85+
url: "https://openrouter.ai/api/v1/models",
86+
headers: (token) => ({ authorization: `Bearer ${token}` }),
87+
},
88+
};
89+
90+
const unknown = [...required].filter((id) => !providers[id]);
91+
if (unknown.length > 0) {
92+
console.error(`unknown providers: ${unknown.join(",")}`);
93+
process.exit(2);
94+
}
95+
96+
const results = [];
97+
for (const id of Object.keys(providers)) {
98+
if (required.has(id) || envFirst(providers[id].env)) {
99+
results.push(await checkProvider(id, providers[id]));
100+
}
101+
}
102+
103+
let failed = false;
104+
for (const result of results) {
105+
const requiredLabel = required.has(result.id) ? "required" : "optional";
106+
console.log(`${result.id}: ${result.status} env=${result.env} ${requiredLabel}`);
107+
if (required.has(result.id) && !result.ok) failed = true;
108+
}
109+
110+
if (failed) {
111+
console.error("release provider secret preflight failed");
112+
process.exit(1);
113+
}

.github/workflows/docs-sync-publish.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,37 @@ permissions:
1616
jobs:
1717
sync-publish-repo:
1818
runs-on: ubuntu-latest
19+
env:
20+
OPENCLAW_DOCS_SYNC_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
1921
steps:
22+
- name: Skip publish sync without token
23+
if: env.OPENCLAW_DOCS_SYNC_TOKEN == ''
24+
run: echo "OPENCLAW_DOCS_SYNC_TOKEN is not configured; skipping docs publish repo sync."
25+
2026
- name: Checkout source repo
27+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
2128
uses: actions/checkout@v6
2229
with:
2330
fetch-depth: 0
2431

2532
- name: Checkout ClawHub docs source
33+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
2634
uses: actions/checkout@v6
2735
with:
2836
repository: openclaw/clawhub
2937
path: clawhub-source
3038
fetch-depth: 1
3139
persist-credentials: false
32-
token: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN || github.token }}
40+
token: ${{ env.OPENCLAW_DOCS_SYNC_TOKEN || github.token }}
3341

3442
- name: Setup Node
43+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
3544
uses: actions/setup-node@v6
3645
with:
3746
node-version: "22.18.0"
3847

3948
- name: Clone publish repo
40-
env:
41-
OPENCLAW_DOCS_SYNC_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
49+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
4250
run: |
4351
set -euo pipefail
4452
for attempt in 1 2 3 4 5; do
@@ -56,6 +64,7 @@ jobs:
5664
exit 1
5765
5866
- name: Sync docs into publish repo
67+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
5968
run: |
6069
clawhub_sha="$(git -C "$GITHUB_WORKSPACE/clawhub-source" rev-parse HEAD)"
6170
node scripts/docs-sync-publish.mjs \
@@ -67,13 +76,16 @@ jobs:
6776
--clawhub-source-sha "$clawhub_sha"
6877
6978
- name: Install docs MDX checker dependency
79+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
7080
working-directory: publish
7181
run: npm install --no-save --package-lock=false @mdx-js/[email protected]
7282

7383
- name: Check publish docs MDX
84+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
7485
run: node "$GITHUB_WORKSPACE/publish/.openclaw-sync/check-docs-mdx.mjs" "$GITHUB_WORKSPACE/publish/docs"
7586

7687
- name: Commit publish repo sync
88+
if: env.OPENCLAW_DOCS_SYNC_TOKEN != ''
7789
working-directory: publish
7890
run: |
7991
set -euo pipefail

0 commit comments

Comments
 (0)