fix(lint): reject malformed oxlint shard env

This commit is contained in:
Vincent Koc
2026-06-02 21:06:14 +02:00
parent d007b9aba3
commit 054e734e53
2 changed files with 66 additions and 22 deletions
+38 -15
View File
@@ -102,15 +102,11 @@ export function createWindowsExtensionShards({
}
export function resolveWindowsExtensionChunkSize(env = process.env) {
const rawValue = env.OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE;
if (rawValue === undefined) {
return DEFAULT_WINDOWS_EXTENSION_CHUNK_SIZE;
}
const parsedValue = Number.parseInt(rawValue, 10);
return Number.isFinite(parsedValue) && parsedValue > 0
? parsedValue
: DEFAULT_WINDOWS_EXTENSION_CHUNK_SIZE;
return resolvePositiveEnvIntWithFallback(
env,
"OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE",
DEFAULT_WINDOWS_EXTENSION_CHUNK_SIZE,
);
}
export function shouldRunOxlintShardsSerial({
@@ -505,22 +501,49 @@ export function resolveShardKillGraceMs(env) {
function resolveNonNegativeEnvInt(env, key, defaultValue) {
const rawValue = env[key];
if (rawValue === undefined) {
if (rawValue === undefined || rawValue === "") {
return defaultValue;
}
const parsedValue = Number.parseInt(rawValue, 10);
return Number.isFinite(parsedValue) && parsedValue >= 0 ? parsedValue : defaultValue;
const text = String(rawValue).trim();
if (!/^\d+$/u.test(text)) {
throw new Error(`${key} must be a non-negative integer; got: ${rawValue}`);
}
const parsedValue = Number(text);
if (!Number.isSafeInteger(parsedValue)) {
throw new Error(`${key} must be a non-negative integer; got: ${rawValue}`);
}
return parsedValue;
}
function resolvePositiveEnvInt(env, key) {
const rawValue = env[key];
if (rawValue === undefined) {
if (rawValue === undefined || rawValue === "") {
return null;
}
const parsedValue = Number.parseInt(rawValue, 10);
return Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : null;
return parsePositiveEnvInt(rawValue, key);
}
function resolvePositiveEnvIntWithFallback(env, key, defaultValue) {
const rawValue = env[key];
if (rawValue === undefined || rawValue === "") {
return defaultValue;
}
return parsePositiveEnvInt(rawValue, key);
}
function parsePositiveEnvInt(rawValue, key) {
const text = String(rawValue).trim();
if (!/^\d+$/u.test(text)) {
throw new Error(`${key} must be a positive integer; got: ${rawValue}`);
}
const parsedValue = Number(text);
if (!Number.isSafeInteger(parsedValue) || parsedValue <= 0) {
throw new Error(`${key} must be a positive integer; got: ${rawValue}`);
}
return parsedValue;
}
function signalChildProcess({ child, signal, useProcessGroup }) {
+28 -7
View File
@@ -197,22 +197,38 @@ describe("run-oxlint", () => {
splitCore: true,
}),
).toBe(2);
expect(() =>
resolveOxlintShardConcurrency({
env: { CI: "true", OPENCLAW_OXLINT_SHARD_CONCURRENCY: "2x" },
platform: "linux",
hostResources: roomyHost,
splitCore: true,
}),
).toThrow("OPENCLAW_OXLINT_SHARD_CONCURRENCY must be a positive integer; got: 2x");
});
it("uses a bounded oxlint shard heartbeat by default", () => {
expect(resolveShardHeartbeatMs({})).toBe(30_000);
expect(resolveShardHeartbeatMs({ OPENCLAW_OXLINT_SHARD_HEARTBEAT_MS: "0" })).toBe(0);
expect(resolveShardHeartbeatMs({ OPENCLAW_OXLINT_SHARD_HEARTBEAT_MS: "5000" })).toBe(5000);
expect(resolveShardHeartbeatMs({ OPENCLAW_OXLINT_SHARD_HEARTBEAT_MS: "bad" })).toBe(30_000);
expect(() => resolveShardHeartbeatMs({ OPENCLAW_OXLINT_SHARD_HEARTBEAT_MS: "5000ms" })).toThrow(
"OPENCLAW_OXLINT_SHARD_HEARTBEAT_MS must be a non-negative integer; got: 5000ms",
);
});
it("uses a bounded oxlint shard timeout by default", () => {
expect(resolveShardTimeoutMs({})).toBe(900_000);
expect(resolveShardTimeoutMs({ OPENCLAW_OXLINT_SHARD_TIMEOUT_MS: "0" })).toBe(0);
expect(resolveShardTimeoutMs({ OPENCLAW_OXLINT_SHARD_TIMEOUT_MS: "5000" })).toBe(5000);
expect(resolveShardTimeoutMs({ OPENCLAW_OXLINT_SHARD_TIMEOUT_MS: "bad" })).toBe(900_000);
expect(() => resolveShardTimeoutMs({ OPENCLAW_OXLINT_SHARD_TIMEOUT_MS: "1e3" })).toThrow(
"OPENCLAW_OXLINT_SHARD_TIMEOUT_MS must be a non-negative integer; got: 1e3",
);
expect(resolveShardKillGraceMs({})).toBe(5_000);
expect(resolveShardKillGraceMs({ OPENCLAW_OXLINT_SHARD_KILL_GRACE_MS: "0" })).toBe(0);
expect(() => resolveShardKillGraceMs({ OPENCLAW_OXLINT_SHARD_KILL_GRACE_MS: "-1" })).toThrow(
"OPENCLAW_OXLINT_SHARD_KILL_GRACE_MS must be a non-negative integer; got: -1",
);
});
it("fails a stuck oxlint shard instead of waiting forever", async () => {
@@ -518,14 +534,19 @@ describe("run-oxlint", () => {
]);
});
it("keeps the default Windows oxlint extension chunk size for invalid overrides", () => {
it("rejects invalid Windows oxlint extension chunk size overrides", () => {
expect(resolveWindowsExtensionChunkSize({})).toBe(8);
expect(
resolveWindowsExtensionChunkSize({ OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE: "0" }),
).toBe(8);
() => resolveWindowsExtensionChunkSize({ OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE: "0" }),
).toThrow("OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE must be a positive integer; got: 0");
expect(
resolveWindowsExtensionChunkSize({ OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE: "abc" }),
).toBe(8);
() =>
resolveWindowsExtensionChunkSize({
OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE: "8 chunks",
}),
).toThrow(
"OPENCLAW_OXLINT_WINDOWS_EXTENSION_CHUNK_SIZE must be a positive integer; got: 8 chunks",
);
});
it("filters tracked targets missing from sparse checkouts", () => {