mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
fix(ci): keep admitted Testboxes alive and enforce current workflow limits (#163021)
This commit is contained in:
parent
70ca83470f
commit
d6411f28a1
12 changed files with 128 additions and 21 deletions
|
|
@ -45,6 +45,10 @@ availability, Blacksmith control-plane health, and downstream queue drains.
|
|||
named memory-heavy command; see `docs/reference/test/remote-proof.md`.
|
||||
That workflow has at most four concurrent leases inside the shared 32-slot
|
||||
Testbox pool. All Testbox profiles cap idle time at 15 minutes.
|
||||
- Allocate through the current OpenClaw wrapper with workflow ref `main`;
|
||||
the source capsule preserves the checkout being tested. Do not dispatch an
|
||||
old workflow ref to bypass spending limits. Queue age is checked before
|
||||
checkout; an admitted lease keeps its job and idle deadlines.
|
||||
- Do not promote an entire workflow family because one command needs more RAM.
|
||||
Keep proven high-memory CI rows scoped to their owning planner and evidence;
|
||||
remeasure before changing their allocation. A 32-class label is not proof of
|
||||
|
|
|
|||
|
|
@ -132,8 +132,6 @@ jobs:
|
|||
|
||||
- name: Bound Testbox idle lifetime
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
TESTBOX_EXPIRES_AT: ${{ needs.admission.outputs.expires_at }}
|
||||
run: node scripts/ci-testbox-budget.mjs configure
|
||||
|
||||
- name: Setup Node environment
|
||||
|
|
|
|||
2
.github/workflows/ci-check-arm-testbox.yml
vendored
2
.github/workflows/ci-check-arm-testbox.yml
vendored
|
|
@ -135,8 +135,6 @@ jobs:
|
|||
persist-credentials: false
|
||||
- name: Bound Testbox idle lifetime
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
TESTBOX_EXPIRES_AT: ${{ needs.admission.outputs.expires_at }}
|
||||
run: node scripts/ci-testbox-budget.mjs configure
|
||||
|
||||
- name: Setup Node environment
|
||||
|
|
|
|||
|
|
@ -84,8 +84,6 @@ jobs:
|
|||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
- name: Bound Testbox idle lifetime
|
||||
env:
|
||||
TESTBOX_EXPIRES_AT: ${{ needs.admission.outputs.expires_at }}
|
||||
run: node scripts/ci-testbox-budget.mjs configure
|
||||
|
||||
- name: Setup Node environment
|
||||
|
|
|
|||
2
.github/workflows/ci-check-testbox.yml
vendored
2
.github/workflows/ci-check-testbox.yml
vendored
|
|
@ -128,8 +128,6 @@ jobs:
|
|||
persist-credentials: false
|
||||
- name: Bound Testbox idle lifetime
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
TESTBOX_EXPIRES_AT: ${{ needs.admission.outputs.expires_at }}
|
||||
run: node scripts/ci-testbox-budget.mjs configure
|
||||
|
||||
- name: Setup Node environment
|
||||
|
|
|
|||
|
|
@ -531,7 +531,9 @@ A supported sibling or `PATH` binary can run directly. The wrapper automatically
|
|||
replaces an outdated selection with the plugin-managed binary; rebuilding the
|
||||
sibling checkout is no longer a prerequisite for proof.
|
||||
|
||||
The `blacksmith:` block in `.crabbox.yaml` already pins the org, workflow, job, and ref defaults, so the explicit flags below are optional. Explicit clean-machine changed-gate parity:
|
||||
The `blacksmith:` block in `.crabbox.yaml` pins the org, workflow, and job defaults.
|
||||
The wrapper enforces workflow ref `main` while syncing the selected local source,
|
||||
so the explicit flags below are optional. Explicit clean-machine changed-gate parity:
|
||||
|
||||
```bash
|
||||
pnpm crabbox:run -- --provider blacksmith-testbox \
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ collisions can leave other slots unused; this is a ceiling, not a promise of
|
|||
Admission expires ten minutes after the workflow was created. A request that
|
||||
waits longer fails before checkout or hydration when its runner starts; it can
|
||||
still incur runner startup cost. This does not remove the queued job immediately.
|
||||
Once the request passes that check, checkout and hydration do not recheck queue
|
||||
age. They remain bounded by the job timeout and idle limit.
|
||||
Stop an abandoned lease by its exact ID instead of leaving a warmup pending.
|
||||
Do not retry in a loop when the pool is full.
|
||||
|
||||
|
|
@ -75,7 +77,10 @@ operating choice; remeasure after the 15-minute idle cap is deployed before
|
|||
making 30 minutes or 8-class the general default.
|
||||
|
||||
These controls cover dispatches using the updated workflows in this repository.
|
||||
Historical refs, other repositories, alternate workflows, and Windows probe
|
||||
The OpenClaw wrapper selects the workflow from `main` for Testbox `run` and
|
||||
`warmup`, overriding configured refs and rejecting explicit historical refs.
|
||||
Its source capsule still reconstructs the checkout being tested. Old wrappers,
|
||||
direct historical-ref dispatches, other repositories, alternate workflows, and Windows probe
|
||||
workflows are outside the shared pool. Organization-wide concurrency, per-token
|
||||
admission, SKU restrictions, and a hard spending stop require provider controls.
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ for later remote commands, sync the current checkout on every run, and stop it
|
|||
before handoff. Let the previous command and its cleanup finish before
|
||||
another synchronization or reuse of that lease.
|
||||
|
||||
Testbox `run` and `warmup` use the workflow from `main` so new allocations
|
||||
inherit the maintained spending limits. The wrapper overrides configured workflow
|
||||
refs and rejects an explicit `--blacksmith-ref` other than `main`. Choose the
|
||||
source revision in your local checkout; its source capsule and frozen dependency
|
||||
install preserve that selection independently of the workflow ref. Explicit
|
||||
workflow and job selection still support the high-memory profile.
|
||||
|
||||
At allocation, the wrapper records the caller task, physical checkout, HEAD,
|
||||
base, dependency inputs, and Testbox preparation fingerprint under
|
||||
`.crabbox/testbox-leases/`. Reuse requires the same task, checkout, base,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export function boundedTestboxIdleMinutes(value) {
|
|||
|
||||
async function main() {
|
||||
if (process.argv[2] === "configure") {
|
||||
assertFreshTestboxAdmission(process.env.TESTBOX_EXPIRES_AT);
|
||||
// The workflow checks queue age before checkout; admitted work keeps its lease.
|
||||
const path = "/tmp/.testbox/idle_timeout";
|
||||
const idle = boundedTestboxIdleMinutes(readFileSync(path, "utf8"));
|
||||
writeFileSync(path, `${idle}\n`);
|
||||
|
|
|
|||
|
|
@ -3774,6 +3774,19 @@ if (provider && !isProviderAdvertised(provider, providers)) {
|
|||
}
|
||||
|
||||
if (canonicalProvider === "blacksmith-testbox") {
|
||||
if (["run", "warmup"].includes(normalizedArgs[0] ?? "")) {
|
||||
const workflowRef = parseCommandInvocation(help.text, normalizedArgs).optionEntries.findLast(
|
||||
({ name }) => name === "blacksmith-ref",
|
||||
);
|
||||
if (workflowRef && workflowRef.value !== "main") {
|
||||
console.error(
|
||||
"[crabbox] Testbox workflow ref must be main so allocations use current spending limits. Omit --blacksmith-ref; the source capsule preserves the checkout being tested.",
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
// Override config/environment refs before binding the allocation receipt.
|
||||
normalizedArgs.splice(commandOptionEnd(normalizedArgs), 0, "--blacksmith-ref=main");
|
||||
}
|
||||
// The delegated provider rejects uploaded scripts before acquiring a lease.
|
||||
if (
|
||||
normalizedArgs[0] === "run" &&
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { spawnSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
import {
|
||||
|
|
@ -98,6 +100,37 @@ describe("Testbox spending admission", () => {
|
|||
expect(() => boundedTestboxIdleMinutes("0")).toThrow(/invalid/);
|
||||
});
|
||||
|
||||
it("configures idle time after an admitted checkout crosses the queue deadline", () => {
|
||||
const script = resolve("scripts/ci-testbox-budget.mjs");
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--input-type=module",
|
||||
"--eval",
|
||||
`
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import { syncBuiltinESMExports } from "node:module";
|
||||
import { pathToFileURL } from "node:url";
|
||||
const read = fs.readFileSync;
|
||||
fs.readFileSync = (path, ...args) =>
|
||||
path === "/tmp/.testbox/idle_timeout" ? "90\\n" : read(path, ...args);
|
||||
fs.writeFileSync = (path, value) => {
|
||||
assert.equal(path, "/tmp/.testbox/idle_timeout");
|
||||
assert.equal(value, "15\\n");
|
||||
console.log("idle setting updated");
|
||||
};
|
||||
syncBuiltinESMExports();
|
||||
process.argv = [process.execPath, ${JSON.stringify(script)}, "configure"];
|
||||
await import(pathToFileURL(process.argv[1]).href);
|
||||
`,
|
||||
],
|
||||
{ encoding: "utf8", env: { TESTBOX_EXPIRES_AT: "1" } },
|
||||
);
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(result.stdout).toContain("idle setting updated");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["ci-check-testbox.yml", "check", "check"],
|
||||
["ci-check-high-memory-testbox.yml", "check", "check-memory"],
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ const azureProviderHelp =
|
|||
"provider: hetzner, aws, azure, local-container, blacksmith-testbox, or cloudflare\n";
|
||||
const fakeRunValueOptionHelp = [
|
||||
"artifact-glob value",
|
||||
"blacksmith-job string",
|
||||
"blacksmith-ref string",
|
||||
"blacksmith-workflow string",
|
||||
"capture-stderr string",
|
||||
"capture-stdout string",
|
||||
"download value",
|
||||
|
|
@ -2388,6 +2390,7 @@ describe("scripts/crabbox-wrapper", () => {
|
|||
"blacksmith-testbox",
|
||||
"--id",
|
||||
id,
|
||||
"--blacksmith-ref=main",
|
||||
"--reclaim",
|
||||
"--shell",
|
||||
"--",
|
||||
|
|
@ -3829,9 +3832,9 @@ esac
|
|||
|
||||
it.each([
|
||||
{
|
||||
scenario: "Blacksmith feature ref",
|
||||
scenario: "Blacksmith maintained workflow",
|
||||
provider: "blacksmith-testbox",
|
||||
args: ["--blacksmith-ref", "feature-branch"],
|
||||
args: ["--blacksmith-ref", "main"],
|
||||
command: ["corepack", "pnpm", "check:changed"],
|
||||
},
|
||||
{ scenario: "local container", provider: "local-container", args: [], command: ["echo ok"] },
|
||||
|
|
@ -3858,6 +3861,61 @@ esac
|
|||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["warmup", "--blacksmith-ref=main", "--blacksmith-ref", "feature-branch"],
|
||||
["run", "-blacksmith-ref=feature-branch", "--sync-only"],
|
||||
])("refuses historical Testbox workflow allocation before delegation: %s", (...args) => {
|
||||
const invocationLog = makeInvocationLog();
|
||||
const result = runDefaultWrapper([...args, "--provider", "blacksmith-testbox"], {
|
||||
env: { OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog },
|
||||
});
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stderr).toContain("Testbox workflow ref must be main");
|
||||
expect(
|
||||
readInvocations(invocationLog).some(
|
||||
(invocation) =>
|
||||
["run", "warmup"].includes(invocation[0] ?? "") && !invocation.includes("--help"),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.each([[], ["--blacksmith-ref=old-workflow", "-blacksmith-ref", "main"]])(
|
||||
"pins Testbox policy independently of configured refs and payload arguments: %j",
|
||||
(...refs) => {
|
||||
const { output } = runSuccessfulDefaultWrapper(
|
||||
[
|
||||
"run",
|
||||
"--provider",
|
||||
"blacksmith-testbox",
|
||||
...refs,
|
||||
"--blacksmith-workflow",
|
||||
".github/workflows/ci-check-high-memory-testbox.yml",
|
||||
"--blacksmith-job",
|
||||
"check",
|
||||
"--",
|
||||
"echo",
|
||||
"--blacksmith-ref",
|
||||
"historical-source",
|
||||
],
|
||||
{
|
||||
configJson: { provider: "blacksmith-testbox", blacksmith: { ref: "old-workflow" } },
|
||||
env: { CRABBOX_BLACKSMITH_REF: "old-environment-ref" },
|
||||
},
|
||||
);
|
||||
const optionEnd = output.args.indexOf("--");
|
||||
expect(output.args.slice(0, optionEnd)).toContain("--blacksmith-ref=main");
|
||||
expect(output.args.slice(0, optionEnd)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"--blacksmith-workflow",
|
||||
".github/workflows/ci-check-high-memory-testbox.yml",
|
||||
"--blacksmith-job",
|
||||
"check",
|
||||
]),
|
||||
);
|
||||
expect(output.args.at(-1)).toContain("historical-source");
|
||||
},
|
||||
);
|
||||
|
||||
it("uses an explicit release base for changed-gate sync and remote Git metadata", () => {
|
||||
const { remoteCommand, result } = runSuccessfulDefaultWrapper(
|
||||
[
|
||||
|
|
@ -6463,14 +6521,7 @@ cp.spawnSync = (command, args, options) => {
|
|||
|
||||
it("uses the temporary full checkout for sparse sync-only runs", () => {
|
||||
const { output, result } = runSuccessfulDefaultWrapper(
|
||||
[
|
||||
"run",
|
||||
"--provider",
|
||||
"blacksmith-testbox",
|
||||
"--blacksmith-ref",
|
||||
"feature-branch",
|
||||
"--sync-only",
|
||||
],
|
||||
["run", "--provider", "blacksmith-testbox", "--blacksmith-ref", "main", "--sync-only"],
|
||||
cleanSparseSyncOptions,
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue