Public: anyone can read this; the crew can change it.
Checks at once and shards per check default to what the pod's CPUs hold, so a big machine needs no override #2551
Check · Review · Land · Live done — Landed and live in production.
The defaults for checks at once and shards per check now follow the pod's CPU count. On the vxrail's 80 cores that's 4 shards and 6 checks, the values the owner chose, so the sentinel stops reporting them as away from their defaults. The type check passes, and so do the capacity, sentinel, asks, limits, board, pipeline, shards and checks suites. The full check runs on the push.
shipped production is serving it deploy 6365650
The defaults for checks at once and shards per check now follow the pod's CPU count. On the vxrail's 80 cores that's 4 shards and 6 checks, the values the owner chose, so the sentinel stops reporting them as away from their defaults. The type check passes, and so do the capacity, sentinel, asks, limits, board, pipeline, shards and checks suites. The full check runs on the push.
What changed
cpuLimit()(src/core/capacity.ts): reads cgroup v2cpu.maxas quota ÷ period, rounded down, at least 1 and never more than the machine has. If there's no quota (max), the file is unreadable or the contents are garbage, it usesos.availableParallelism(). It's built likememoryLimitBytes().- New defaults (
shardsForCpus,checksForCpus): shards aremin(4, cpus); checks at once arefloor(cpus / (shards × 3)), kept between 1 and 16. So 80 CPUs give 4 and 6, 16 give 4 and 1, and 4 give 4 and 1. - What still wins:
capacityDefaultuses these only when the environment variable doesn't set a valid value, and a stored value still overrides both. The sentinel and the settings asks already compare againstcapacityDefault, so they pick up the new defaults with no change of their own. src/main.tsstarts the checks service at the CPU-sized default instead of a fixed 1.- Help lines: both now say how their default is reached.
Things to look at
- Shard count is now always passed down.
capacityEnvused to setFORGE_CHECK_SHARDSonly when someone had set a value. Now it always passes the shard count in force. Without this, clearing the owner's stored 4 would have sent checks back to one shard per cell CPU (up to 32), so clearing it would have changed behaviour. For a check, the default is sized to what its cell holds (workerCpus(cpuLimit(), cellCpus)fromscripts/shards.ts), so a small cell is never handed the pod's count. A stored or environment value is passed as it is, as before. - Possibly slower check: the forge's own check, in a 16-CPU cell, now defaults to 4 shards; before, it sized itself to the cell (12). If that's too slow, set
checks.shardshigher. - Tests pin the CPU count. The test preload calls a new
provideCpusForTest(4)so the defaults are the same on any machine. This sandbox reports 48 CPUs, which would have made the stored "4" in the sentinel tests equal the default. - One existing test changed because it encoded the old behaviour:
test/capacity.test.tsexpected an empty env, and""in the change log, after a reset. It now expects the sized default, and clearsFORGE_CHECK_SHARDSfrom the environment while it checks. - New tests (
test/capacity-cpus.test.ts):
- the hand-calculated table for 1, 2, 4, 16, 80 and 1000 CPUs
- the environment winning over the CPUs
- a stored value overriding both the default and a cell's smaller count
- the sentinel ignoring 4 and 6 on 80 CPUs and flagging 8 shards against "default 4"
- cpuLimit with fractional, over-machine, max, garbage and missing cpu.max files
Not done
- No cgroup v1 CPU fallback (
cpu.cfs_quota_us); the issue asked for v2 only. - The owner's stored values on production are left in place. Clearing them is now safe and changes nothing on the vxrail.
To try it: bun test test/capacity-cpus.test.ts. On /settings/capacity, clear checks.shards and checks.concurrency: the defaults shown are what this pod's CPUs give.
🤖 Generated with Claude Code
The defaults for checks at once and shards per check now follow the pod's CPU count. On the vxrail's 80 cores that gives 4 shards and 6 checks, the values the owner chose, so the sentinel stops reporting them as away from their defaults. This push also fixes the restore race behind the last red check. The full check hasn't run on it yet.
The CPU-sized defaults
cpuLimit()(src/core/capacity.ts): reads cgroup v2cpu.maxas quota ÷ period, rounded down, at least 1 and never more than the machine has. If there's no quota (max), the file is unreadable or the contents are garbage, it usesos.availableParallelism().- New defaults (
shardsForCpus,checksForCpus): shards aremin(4, cpus); checks at once arefloor(cpus / (shards × 3)), kept between 1 and 16. So 80 CPUs give 4 and 6, 16 give 4 and 1, and 4 give 4 and 1. - What still wins: the environment variable, then a stored value. The sentinel and the settings asks already compare against
capacityDefault, so they use the new defaults with no change. src/main.tsstarts the checks service at the CPU-sized default instead of a fixed 1.- Help lines: both now say how their default is reached.
Things to look at
- Shard count is always passed down now.
capacityEnvused to setFORGE_CHECK_SHARDSonly when someone had set a value; now it always passes the count in force. Without this, clearing the owner's stored 4 would have sent checks back to one shard per cell CPU. A check's default is sized to what its cell holds (workerCpus(cpuLimit(), cellCpus)), so a small cell is never handed the pod's count. - Possibly slower check: the forge's own check, in a 16-CPU cell, now defaults to 4 shards instead of the 12 it used to give itself. If that's too slow, set
checks.shardshigher. - Tests pin the CPU count. The test preload calls a new
provideCpusForTest(4), so the defaults are the same on any machine. - One existing test changed because it encoded the old behaviour:
test/capacity.test.tsexpected an emptycapacityEnvand""in the change log after a reset. It now expects the sized default. - New tests (
test/capacity-cpus.test.ts):
- the hand-calculated table for 1, 2, 4, 16, 80 and 1000 CPUs
- the environment winning over the CPUs
- a stored value overriding both the default and a cell's smaller count
- the sentinel ignoring 4 and 6 on 80 CPUs and flagging 8 shards against "default 4"
- cpuLimit with fractional, over-machine, max, garbage and missing cpu.max files
The restore race that failed the check
- Cause: "repositories come back in the manifest's order" failed because of a real race in
src/snapshot/restore.ts, which is on main too. Repositories are cloned four at a time, and the restore threw whichever clone failed first. On a loaded runneralice/secondcould fail beforealice/first. - Fix: after a failure no new clones start, the ones already running finish, and the error names the failure earliest in the manifest.
- Test: a new one in
test/snapshot-restore-parallel.test.tsmakes the first clone fail last. It fails without the fix and passes with it. - Shard 14 is unexplained. It was stopped (exit 143) without naming a test. That check ran on production's main code, so this branch didn't choose its shard count, and I made no change for it.
Not done
- No cgroup v1 CPU fallback (
cpu.cfs_quota_us); the issue asked for v2 only. - The owner's stored values on production are left in place. Clearing them is now safe and changes nothing on the vxrail.
To try it: bun test test/capacity-cpus.test.ts test/snapshot-restore-parallel.test.ts. Both pass here, as does the type check. Then, on /settings/capacity, clear checks.shards and checks.concurrency: the defaults shown are what this pod's CPUs give.
🤖 Generated with Claude Code
Approved from the diff only: I did not open the preview or the screenshots. The change does what #2549 asked. Shards default to min(4, cpus) and checks to floor(cpus/(shards×3)), held to 1–16. The environment still wins over the CPU-sized default, and a stored value wins over both. The sentinel and asks pick up the new defaults through capacityDefault. A check is handed the shard count its cell holds. The tests pin the CPU count in the preload, so they behave the same on any machine. Every new behaviour has a test, including the cpu.max parsing and the restore ordering. The restore race fix is outside the issue, but it is the red test this pull met, so it belongs here. Two things I would do next: 1) The settings page should show the effective default as a number. The help text says how it is derived, but a person clearing the field should see "default 4" or "default 6" on the page. 2) Add a cgroup v1 fallback, or log which source cpuLimit() used. Today a pod without cgroup v2 silently reads the machine count, and the checks default is then sized from the whole host.
The defaults for checks at once and shards per check now follow the pod's CPU count. On the vxrail's 80 cores that gives 4 shards and 6 checks, the values the owner chose, so the sentinel stops reporting them as away from their defaults. The branch now has main merged in. After the merge, the type check passes and 144 tests across the capacity, sentinel, asks, limits, board, pipeline, checks and snapshot-restore suites pass; the forge's full check runs on this push.
The CPU-sized defaults
cpuLimit()(src/core/capacity.ts): reads cgroup v2cpu.maxas quota ÷ period, rounded down, at least 1 and never more than the machine has. If there's no quota (max), the file is unreadable or the contents are garbage, it usesos.availableParallelism().- New defaults (
shardsForCpus,checksForCpus): shards aremin(4, cpus); checks at once arefloor(cpus / (shards × 3)), kept between 1 and 16. So 80 CPUs give 4 and 6, 16 give 4 and 1, and 4 give 4 and 1. - What still wins: the environment variable, then a stored value. The sentinel and the settings asks already compare against
capacityDefault, so they use the new defaults with no change. src/main.tsstarts the checks service at the CPU-sized default instead of a fixed 1.- Help lines: both now say how their default is reached.
Merging main
capacityDefaultnow holds both sides: main's crew concurrency sized from memory (crewForMemory) and this branch's check settings sized from CPUs.src/main.ts: main had added the samecapacityDefaultimport, so the duplicate is gone.- The restore race: main fixed the same race this branch had fixed (it named the clone that failed first, not the earliest in the manifest), so
src/snapshot/restore.tsis main's version. The test added here, where clones fail at once, is kept and passes against main's code.
Things to look at
- Shard count is always passed down now.
capacityEnvused to setFORGE_CHECK_SHARDSonly when someone had set a value; now it always passes the count in force. Without this, clearing the owner's stored 4 would have sent checks back to one shard per cell CPU. A check's default is sized to what its cell holds (workerCpus(cpuLimit(), cellCpus)), so a small cell is never handed the pod's count. - Possibly slower check: the forge's own check, in a 16-CPU cell, now defaults to 4 shards instead of the 12 it used to give itself. If that's too slow, set
checks.shardshigher. - Tests pin the CPU count. The test preload calls a new
provideCpusForTest(4), so the defaults are the same on any machine. - One existing test changed because it encoded the old behaviour:
test/capacity.test.tsexpected an emptycapacityEnvand""in the change log after a reset. It now expects the sized default. - New tests (
test/capacity-cpus.test.ts):
- the hand-calculated table for 1, 2, 4, 16, 80 and 1000 CPUs
- the environment winning over the CPUs
- a stored value overriding both the default and a cell's smaller count
- the sentinel ignoring 4 and 6 on 80 CPUs and flagging 8 shards against "default 4"
- cpuLimit with fractional, over-machine, max, garbage and missing cpu.max files
Not done
- No cgroup v1 CPU fallback (
cpu.cfs_quota_us); the issue asked for v2 only. - The owner's stored values on production are left in place. Clearing them is now safe and changes nothing on the vxrail.
To try it: bun test test/capacity-cpus.test.ts. Then, on /settings/capacity, clear checks.shards and checks.concurrency: the defaults shown are what this pod's CPUs give.
🤖 Generated with Claude Code
Approved from the diff; I could not reach the preview from my sandbox, and the change is a setting behind admin sign-in, with no new page. The defaults follow the CPUs (shards min(4,cpus), checks floor(cpus/(shards×3)) held to 1–16). The environment still wins over them, a stored value wins over both, and a check's cell caps its default. The tests pin the CPU count, so they do not depend on the machine. The table, the cgroup parsing and the sentinel comparison are covered. Two things I would do next: (1) wrap the long concurrency: line in src/main.ts, and drop the unused ChecksOptions.concurrency ?? 1 fallback if main.ts always passes it; (2) say on /settings/capacity what the current default resolves to on this pod, so clearing a value shows its result before the owner clears it.
The defaults for checks at once and shards per check now follow the pod's CPU count. On the vxrail's 80 cores that's 4 shards and 6 checks, the values the owner chose, so the sentinel stops reporting them as away from their defaults. The type check passes, and so do the capacity, sentinel, asks, limits, board, pipeline, shards and checks suites. The full check runs on the push.
What changed
cpuLimit()(src/core/capacity.ts): reads cgroup v2cpu.maxas quota ÷ period, rounded down, at least 1 and never more than the machine has. If there's no quota (max), the file is unreadable or the contents are garbage, it usesos.availableParallelism(). It's built likememoryLimitBytes().shardsForCpus,checksForCpus): shards aremin(4, cpus); checks at once arefloor(cpus / (shards × 3)), kept between 1 and 16. So 80 CPUs give 4 and 6, 16 give 4 and 1, and 4 give 4 and 1.capacityDefaultuses these only when the environment variable doesn't set a valid value, and a stored value still overrides both. The sentinel and the settings asks already compare againstcapacityDefault, so they pick up the new defaults with no change of their own.src/main.tsstarts the checks service at the CPU-sized default instead of a fixed 1.Things to look at
capacityEnvused to setFORGE_CHECK_SHARDSonly when someone had set a value. Now it always passes the shard count in force. Without this, clearing the owner's stored 4 would have sent checks back to one shard per cell CPU (up to 32), so clearing it would have changed behaviour. For a check, the default is sized to what its cell holds (workerCpus(cpuLimit(), cellCpus)fromscripts/shards.ts), so a small cell is never handed the pod's count. A stored or environment value is passed as it is, as before.checks.shardshigher.provideCpusForTest(4)so the defaults are the same on any machine. This sandbox reports 48 CPUs, which would have made the stored "4" in the sentinel tests equal the default.test/capacity.test.tsexpected an empty env, and""in the change log, after a reset. It now expects the sized default, and clearsFORGE_CHECK_SHARDSfrom the environment while it checks.test/capacity-cpus.test.ts):- the hand-calculated table for 1, 2, 4, 16, 80 and 1000 CPUs - the environment winning over the CPUs - a stored value overriding both the default and a cell's smaller count - the sentinel ignoring 4 and 6 on 80 CPUs and flagging 8 shards against "default 4" -
cpuLimitwith fractional, over-machine,max, garbage and missingcpu.maxfilesNot done
cpu.cfs_quota_us); the issue asked for v2 only.To try it:
bun test test/capacity-cpus.test.ts. On/settings/capacity, clearchecks.shardsandchecks.concurrency: the defaults shown are what this pod's CPUs give.🤖 Generated with Claude Code
Closes #2549