Skip to content

Commit dfd457a

Browse files
authored
perf(ci): repair and re-key the extension-boundary sticky lane (#109804)
check-additional-extension-package-boundary is the light-run critical-path pole. Measured on runs 29564442266/29564411446/29564259862 (8 vCPU, sticky mounts 429-failing): Setup Node ~40s cold install, shard step 142-161s = ~101s cold plugin-sdk dts prep (nine parallel tsgo builds) + ~58s compiling 122 plugins at concurrency 6 + ~1s canary; warm actions/cache path on a main push (29551077288) runs the shard in 67s. Levers chosen from that data: - Re-key the ext-boundary sticky disk from per-PR/per-config keys (v1) to one O(1) protected key (v2), following the node_modules snapshot fix in #109752. Blacksmith's installation-wide 1000-disk cap is saturated and every mount 429s; per-PR keys are a direct contributor. The config/scripts/ lockfile hash moves from the key into the in-job .source-trees marker next to the existing tree-OID + node/pnpm gate. Single semantic writer: the boundary lane commits explicitly (true, not if-missing) on protected pushes only; PR mounts and the check-lint consumer stay read-only, and the seed step is now writer-only (saves ~11s per PR cold run). - Bump the lane 8 -> 32 vCPU and raise OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY 6 -> 16: both the parallel dts prep and the 122 plugin compiles scale with cores at similar billed core-minutes (same precedent as check-lint/deps). Expected pole time: warm PR runs (protected snapshot tracks every main push) drop to roughly checkout + install-skip + restore + incremental shard, well under 2 minutes; cold runs that touch boundary trees shrink via the core bump. Guarded in ci-workflow-guards so the O(1) key, single-writer commit, and fingerprint composition cannot silently regress.
1 parent 5024c10 commit dfd457a

2 files changed

Lines changed: 118 additions & 27 deletions

File tree

.github/workflows/ci.yml

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,21 +1977,28 @@ jobs:
19771977

19781978
# check-lint's shard runner rebuilds the same plugin-sdk boundary
19791979
# artifacts the boundary lane snapshots (~72s cold); restore them from
1980-
# the shared sticky. Strictly read-only (commit: false): if this lane
1981-
# could commit a freshly formatted disk, a cold-key race with the
1982-
# boundary lane would permanently seed an empty, marker-less snapshot
1983-
# that if-missing then refuses to repair.
1980+
# the shared sticky. Strictly read-only (commit: false): only the
1981+
# protected boundary lane may publish this repository-global snapshot.
19841982
- name: Mount extension boundary sticky disk
19851983
if: matrix.task == 'lint' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
19861984
uses: useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f # v1.4.0
19871985
with:
1988-
key: ${{ github.repository }}-ext-boundary-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
1986+
# One stable disk for the whole repository. The v1 per-PR/per-config
1987+
# keys minted a new backing disk for every PR and toolchain change
1988+
# and helped saturate Blacksmith's installation-wide sticky-disk
1989+
# budget, 429-failing every mount. Snapshot validity lives in the
1990+
# in-job marker checked below instead of the key.
1991+
key: ${{ github.repository }}-ext-boundary-v2
19891992
path: /var/tmp/openclaw-ext-boundary
19901993
commit: "false"
19911994

19921995
- name: Restore extension boundary artifacts from sticky disk
19931996
if: matrix.task == 'lint' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
19941997
shell: bash
1998+
env:
1999+
# Config/toolchain inputs the tree-OID gate below cannot see; must
2000+
# stay identical to the boundary lane's fingerprint composition.
2001+
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
19952002
run: |
19962003
set -euo pipefail
19972004
sticky_root=/var/tmp/openclaw-ext-boundary
@@ -2002,7 +2009,7 @@ jobs:
20022009
# Same tree-OID gate as the boundary lane: restore only artifacts
20032010
# produced from identical generative sources, so stale snapshots can
20042011
# neither false-skip rebuilds nor leave ghost declarations.
2005-
current_trees="$({ git rev-parse HEAD:src/plugin-sdk HEAD:packages HEAD:extensions HEAD:src/auto-reply HEAD:src/types HEAD:src/plugins/types.ts HEAD:src/video-generation HEAD:src/channels; node --version; corepack pnpm --version 2>/dev/null || pnpm --version; })"
2012+
current_trees="$({ git rev-parse HEAD:src/plugin-sdk HEAD:packages HEAD:extensions HEAD:src/auto-reply HEAD:src/types HEAD:src/plugins/types.ts HEAD:src/video-generation HEAD:src/channels; node --version; corepack pnpm --version 2>/dev/null || pnpm --version; echo "$BOUNDARY_CONFIG_HASH"; })"
20062013
if [ ! -f "$sticky_root/.source-trees" ] || [ "$current_trees" != "$(cat "$sticky_root/.source-trees")" ]; then
20072014
echo "boundary source trees changed since snapshot; lint prepares cold"
20082015
exit 0
@@ -2182,7 +2189,11 @@ jobs:
21822189
runner: blacksmith-8vcpu-ubuntu-2404
21832190
- check_name: check-additional-extension-package-boundary
21842191
group: extension-package-boundary
2185-
runner: blacksmith-8vcpu-ubuntu-2404
2192+
# Light-run critical-path pole: cold runs spend ~100s in nine
2193+
# parallel plugin-sdk dts builds plus ~58s compiling 122 plugins
2194+
# at concurrency 6 on 8 vCPUs. Both phases scale with cores at
2195+
# similar billed core-minutes.
2196+
runner: blacksmith-32vcpu-ubuntu-2404
21862197
- check_name: check-additional-runtime-topology-architecture
21872198
group: runtime-topology-architecture
21882199
runner: blacksmith-4vcpu-ubuntu-2404
@@ -2207,19 +2218,30 @@ jobs:
22072218
if: matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
22082219
uses: useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f # v1.4.0
22092220
with:
2210-
# Coarse toolchain key only: source changes are meant to hit a stale
2211-
# snapshot, whose restored artifacts rebuild incrementally. PR scope
2212-
# keeps feature snapshots separate from protected pushes.
2213-
key: ${{ github.repository }}-ext-boundary-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
2221+
# One stable disk for the whole repository. The v1 per-PR/per-config
2222+
# keys minted a new backing disk for every PR and toolchain change
2223+
# and helped saturate Blacksmith's installation-wide sticky-disk
2224+
# budget, 429-failing every mount. Snapshot validity lives in the
2225+
# in-job marker checked below instead of the key, so source and
2226+
# toolchain changes refresh this disk in place.
2227+
key: ${{ github.repository }}-ext-boundary-v2
22142228
path: /var/tmp/openclaw-ext-boundary
2215-
# v1.4.0 skips commit after failed/cancelled steps, so an incomplete
2216-
# first hydration cannot seed this key.
2217-
commit: if-missing
2229+
# Single semantic writer: only protected pushes commit, so
2230+
# pull_request clones stay read-only and the snapshot tracks main.
2231+
# Explicit true (not on-change/if-missing) because the allocated-byte
2232+
# heuristic can miss a same-size refresh, permanently stranding
2233+
# consumers on a stale marker. v1.4.0 skips commit after
2234+
# failed/cancelled steps, so a broken build cannot poison this key.
2235+
commit: ${{ github.event_name != 'pull_request' && 'true' || 'false' }}
22182236

22192237
- name: Restore extension boundary artifacts from sticky disk
22202238
id: boundary-sticky-restore
22212239
if: matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
22222240
shell: bash
2241+
env:
2242+
# Config/toolchain inputs the tree-OID gate below cannot see; must
2243+
# stay identical to the seed step and check-lint's restore gate.
2244+
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
22232245
run: |
22242246
set -euo pipefail
22252247
sticky_root=/var/tmp/openclaw-ext-boundary
@@ -2229,12 +2251,12 @@ jobs:
22292251
fi
22302252
# Restore only when the generative source trees AND the runner
22312253
# toolchain match the snapshotting run. Tree OIDs cover source
2232-
# content exactly; the node/pnpm versions cover toolchain-only
2233-
# changes (the sticky key already hashes config/scripts/lockfile, so
2234-
# those force a fresh key). Restored artifacts are valid by
2235-
# construction: no clock-skew mtime race can false-skip a rebuild,
2236-
# and deleted/renamed sources or a toolchain bump build cold.
2237-
current_trees="$({ git rev-parse HEAD:src/plugin-sdk HEAD:packages HEAD:extensions HEAD:src/auto-reply HEAD:src/types HEAD:src/plugins/types.ts HEAD:src/video-generation HEAD:src/channels; node --version; corepack pnpm --version 2>/dev/null || pnpm --version; })"
2254+
# content exactly; the node/pnpm versions and the config hash cover
2255+
# toolchain/config/scripts/lockfile changes the OIDs cannot see.
2256+
# Restored artifacts are valid by construction: no clock-skew mtime
2257+
# race can false-skip a rebuild, and deleted/renamed sources or a
2258+
# toolchain bump build cold.
2259+
current_trees="$({ git rev-parse HEAD:src/plugin-sdk HEAD:packages HEAD:extensions HEAD:src/auto-reply HEAD:src/types HEAD:src/plugins/types.ts HEAD:src/video-generation HEAD:src/channels; node --version; corepack pnpm --version 2>/dev/null || pnpm --version; echo "$BOUNDARY_CONFIG_HASH"; })"
22382260
if [ ! -f "$sticky_root/.source-trees" ] || [ "$current_trees" != "$(cat "$sticky_root/.source-trees")" ]; then
22392261
echo "boundary source trees changed since snapshot; building cold"
22402262
echo "restored=false" >> "$GITHUB_OUTPUT"
@@ -2310,7 +2332,9 @@ jobs:
23102332
RUN_PROMPT_SNAPSHOTS: ${{ needs.preflight.outputs.run_prompt_snapshots }}
23112333
OPENCLAW_ADDITIONAL_BOUNDARY_SHARD: ${{ matrix.boundary_shard || '' }}
23122334
OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY: 4
2313-
OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY: 6
2335+
# Matches the boundary lane's 32 vCPU runner (cores/2); the
2336+
# script's default caps at 6 to protect laptops.
2337+
OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY: 16
23142338
shell: bash
23152339
run: |
23162340
set -euo pipefail
@@ -2401,16 +2425,22 @@ jobs:
24012425
24022426
exit "$failures"
24032427
2404-
# commit: if-missing snapshots only the first run per key; seed the
2405-
# payload before the sticky post-action flushes. rsync -aR mirrors the
2406-
# repo-relative layout the restore step replays.
2428+
# Only the protected writer refreshes the snapshot (pull_request mounts
2429+
# never commit, so seeding there would burn wall clock on a discarded
2430+
# clone). Seed the payload before the sticky post-action flushes.
2431+
# rsync -aR mirrors the repo-relative layout the restore step replays.
24072432
- name: Seed extension boundary sticky disk
2408-
if: success() && steps.boundary-sticky-restore.outputs.restored == 'false' && matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
2433+
if: success() && steps.boundary-sticky-restore.outputs.restored == 'false' && matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' && github.repository == 'openclaw/openclaw'
24092434
shell: bash
2435+
env:
2436+
# Must stay identical to the restore gates above.
2437+
BOUNDARY_CONFIG_HASH: ${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'package.json', 'pnpm-lock.yaml') }}
24102438
run: |
24112439
set -euo pipefail
24122440
sticky_root=/var/tmp/openclaw-ext-boundary
2413-
rm -rf "$sticky_root/dist" "$sticky_root/packages" "$sticky_root/extensions" "$sticky_root/.snapshot-ready"
2441+
# A stale marker must not survive a mid-seed failure: drop readiness
2442+
# first so a partial payload can never be restored as valid.
2443+
rm -rf "$sticky_root/.snapshot-ready" "$sticky_root/.source-trees" "$sticky_root/dist" "$sticky_root/packages" "$sticky_root/extensions"
24142444
if [ -d dist/plugin-sdk ]; then
24152445
rsync -aR dist/plugin-sdk "$sticky_root/"
24162446
fi
@@ -2419,7 +2449,7 @@ jobs:
24192449
fi
24202450
find extensions -maxdepth 3 \( -name '.boundary-tsc.tsbuildinfo' -o -name '.boundary-tsc.stamp' \) \
24212451
-exec rsync -aR {} "$sticky_root/" \;
2422-
{ git rev-parse HEAD:src/plugin-sdk HEAD:packages HEAD:extensions HEAD:src/auto-reply HEAD:src/types HEAD:src/plugins/types.ts HEAD:src/video-generation HEAD:src/channels; node --version; corepack pnpm --version 2>/dev/null || pnpm --version; } > "$sticky_root/.source-trees"
2452+
{ git rev-parse HEAD:src/plugin-sdk HEAD:packages HEAD:extensions HEAD:src/auto-reply HEAD:src/types HEAD:src/plugins/types.ts HEAD:src/video-generation HEAD:src/channels; node --version; corepack pnpm --version 2>/dev/null || pnpm --version; echo "$BOUNDARY_CONFIG_HASH"; } > "$sticky_root/.source-trees"
24232453
touch "$sticky_root/.snapshot-ready"
24242454
24252455
# Validate docs (format, lint, broken links) only when docs files changed.

test/scripts/ci-workflow-guards.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,6 +2383,67 @@ describe("ci workflow guards", () => {
23832383
expect(source).toContain("blacksmith-8vcpu-windows-2025");
23842384
});
23852385

2386+
it("keeps the extension boundary sticky disk on one protected key", () => {
2387+
const workflow = readCiWorkflow();
2388+
const additionalJob = workflow.jobs["check-additional-shard"];
2389+
const checkShardJob = workflow.jobs["check-shard"];
2390+
2391+
// Light-run pole: cold prep + 122 plugin compiles scale with cores at
2392+
// similar billed core-minutes.
2393+
expect(additionalJob.strategy.matrix.include).toContainEqual({
2394+
check_name: "check-additional-extension-package-boundary",
2395+
group: "extension-package-boundary",
2396+
runner: "blacksmith-32vcpu-ubuntu-2404",
2397+
});
2398+
const runStep = additionalJob.steps.find(
2399+
(step: WorkflowStep) => step.name === "Run additional check shard",
2400+
);
2401+
expect(runStep.env.OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY).toBe(16);
2402+
2403+
// O(1) disks: Blacksmith caps sticky disks per installation, and the old
2404+
// per-PR/per-config keys minted new disks until every mount 429-failed
2405+
// fleet-wide. Snapshot validity lives in the in-job marker, not the key.
2406+
const boundaryMount = additionalJob.steps.find(
2407+
(step: WorkflowStep) => step.name === "Mount extension boundary sticky disk",
2408+
);
2409+
const lintMount = checkShardJob.steps.find(
2410+
(step: WorkflowStep) => step.name === "Mount extension boundary sticky disk",
2411+
);
2412+
expect(boundaryMount.with.key).toBe("${{ github.repository }}-ext-boundary-v2");
2413+
expect(lintMount.with.key).toBe(boundaryMount.with.key);
2414+
// Single semantic writer: protected pushes commit explicitly (not
2415+
// on-change/if-missing, whose allocated-byte heuristic can strand a stale
2416+
// marker); PR clones and the lint consumer stay read-only.
2417+
expect(boundaryMount.with.commit).toBe(
2418+
"${{ github.event_name != 'pull_request' && 'true' || 'false' }}",
2419+
);
2420+
expect(lintMount.with.commit).toBe("false");
2421+
2422+
// The key no longer hashes config/scripts/lockfile, so every gate must
2423+
// compose the identical marker fingerprint or restores silently tear.
2424+
const restoreStep = additionalJob.steps.find(
2425+
(step: WorkflowStep) => step.name === "Restore extension boundary artifacts from sticky disk",
2426+
);
2427+
const lintRestoreStep = checkShardJob.steps.find(
2428+
(step: WorkflowStep) => step.name === "Restore extension boundary artifacts from sticky disk",
2429+
);
2430+
const seedStep = additionalJob.steps.find(
2431+
(step: WorkflowStep) => step.name === "Seed extension boundary sticky disk",
2432+
);
2433+
const configHash = seedStep.env.BOUNDARY_CONFIG_HASH;
2434+
expect(configHash).toContain("hashFiles(");
2435+
expect(configHash).toContain("pnpm-lock.yaml");
2436+
expect(restoreStep.env.BOUNDARY_CONFIG_HASH).toBe(configHash);
2437+
expect(lintRestoreStep.env.BOUNDARY_CONFIG_HASH).toBe(configHash);
2438+
for (const gate of [restoreStep, lintRestoreStep, seedStep]) {
2439+
expect(gate.run).toContain('echo "$BOUNDARY_CONFIG_HASH"');
2440+
}
2441+
// Seeding is writer-only work: PR mounts never commit, so seeding there
2442+
// would burn wall clock on a discarded clone.
2443+
expect(seedStep.if).toContain("github.event_name != 'pull_request'");
2444+
expect(seedStep.if).toContain("steps.boundary-sticky-restore.outputs.restored == 'false'");
2445+
});
2446+
23862447
it("runs the session accessor ratchet as a visible additional check", () => {
23872448
const workflow = readCiWorkflow();
23882449
const additionalJob = workflow.jobs["check-additional-shard"];

0 commit comments

Comments
 (0)