Skip to content

Commit e24582d

Browse files
committed
fix(crabbox): preflight sparse sync disk space
1 parent 3e9b197 commit e24582d

3 files changed

Lines changed: 86 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai
4545

4646
### Fixes
4747

48+
- Release/CI/E2E: fail early when Crabbox sparse-sync full checkouts do not have enough local disk, with guidance for moving the sync root.
4849
- Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.
4950
- Scripts/UI: forward direct wrapper SIGHUP shutdown to child processes so terminal hangups do not leave wrapped dev commands running.
5051
- Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.

scripts/crabbox-wrapper.mjs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
readFileSync,
1111
readdirSync,
1212
rmSync,
13+
statfsSync,
1314
statSync,
1415
utimesSync,
1516
writeFileSync,
@@ -1937,8 +1938,60 @@ function fullCheckoutSyncRoot() {
19371938
return root;
19381939
}
19391940

1941+
function parsePositiveIntegerEnv(name, fallback) {
1942+
const raw = process.env[name]?.trim();
1943+
if (!raw) {
1944+
return fallback;
1945+
}
1946+
if (!/^\d+$/u.test(raw)) {
1947+
throw new Error(`${name} must be a non-negative integer byte count, got ${JSON.stringify(raw)}`);
1948+
}
1949+
const parsed = Number.parseInt(raw, 10);
1950+
return parsed;
1951+
}
1952+
1953+
function formatByteCount(bytes) {
1954+
if (bytes < 1024) {
1955+
return `${bytes} B`;
1956+
}
1957+
const units = ["KiB", "MiB", "GiB", "TiB"];
1958+
let value = bytes / 1024;
1959+
let unitIndex = 0;
1960+
while (value >= 1024 && unitIndex < units.length - 1) {
1961+
value /= 1024;
1962+
unitIndex += 1;
1963+
}
1964+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[unitIndex]}`;
1965+
}
1966+
1967+
function assertFullCheckoutSyncDisk(root) {
1968+
const requiredBytes = parsePositiveIntegerEnv(
1969+
"OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES",
1970+
1024 * 1024 * 1024,
1971+
);
1972+
if (requiredBytes === 0) {
1973+
return;
1974+
}
1975+
const stats = statfsSync(root);
1976+
const freeBytes = stats.bavail * stats.bsize;
1977+
if (freeBytes >= requiredBytes) {
1978+
return;
1979+
}
1980+
throw new Error(
1981+
[
1982+
"insufficient free disk for Crabbox sparse-sync full checkout",
1983+
`root=${root}`,
1984+
`free=${formatByteCount(freeBytes)}`,
1985+
`required=${formatByteCount(requiredBytes)}`,
1986+
"set OPENCLAW_CRABBOX_SYNC_TMPDIR to a roomier filesystem or lower OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES if you know this checkout fits",
1987+
].join("; "),
1988+
);
1989+
}
1990+
19401991
function prepareFullCheckoutForSync(options = {}) {
1941-
const dir = mkdtempSync(resolve(fullCheckoutSyncRoot(), "openclaw-crabbox-sync-"));
1992+
const syncRoot = fullCheckoutSyncRoot();
1993+
assertFullCheckoutSyncDisk(syncRoot);
1994+
const dir = mkdtempSync(resolve(syncRoot, "openclaw-crabbox-sync-"));
19421995
let active = false;
19431996

19441997
function create() {

test/scripts/crabbox-wrapper.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2492,6 +2492,37 @@ describe.concurrent("scripts/crabbox-wrapper", () => {
24922492
}
24932493
});
24942494

2495+
it("fails sparse-sync full checkout early when the sync root is too low on disk", () => {
2496+
const syncRoot = path.join(repoRoot, ".crabbox-test-low-disk-sync-root");
2497+
rmSync(syncRoot, { recursive: true, force: true });
2498+
try {
2499+
const result = runWrapper(
2500+
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
2501+
["run", "--provider", "aws", "--", "echo ok"],
2502+
{
2503+
env: {
2504+
OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES: "999999999999999",
2505+
OPENCLAW_CRABBOX_SYNC_TMPDIR: syncRoot,
2506+
},
2507+
gitResponses: {
2508+
[GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" },
2509+
[GIT_STATUS_PORCELAIN_KEY]: { stdout: "" },
2510+
},
2511+
},
2512+
);
2513+
2514+
expect(result.status).toBe(1);
2515+
expect(result.stderr).toContain(
2516+
"insufficient free disk for Crabbox sparse-sync full checkout",
2517+
);
2518+
expect(result.stderr).toContain("OPENCLAW_CRABBOX_SYNC_TMPDIR");
2519+
expect(result.stderr).toContain("OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES");
2520+
expect(readdirSync(syncRoot)).toEqual([]);
2521+
} finally {
2522+
rmSync(syncRoot, { recursive: true, force: true });
2523+
}
2524+
});
2525+
24952526
(process.platform === "win32" ? it.skip : it)(
24962527
"recreates sparse-sync temporary full checkouts that disappear while Crabbox is running",
24972528
() => {

0 commit comments

Comments
 (0)