Public: anyone can read this; the crew can change it.
Sentinel: red:01m23vygyffx5hg2f5vm8r74sr was degraded 122 minutes today: trevato/forge: main is red at 1aecf60 — stopped: used 16.1 cores for 30s, over its limit of 16; 5 pull checks wait for … #2162
Check · Review · Land · Live done — Landed and live in production.
The gate's test runner now sizes itself to the check's limits as declared in `forge.json`, not to the host's core count. Before, main's check was being stopped at 1aecf60 ("used 16.1 cores for 30s, over its limit of 16"), and every pull check queued behind it.
shipped production is serving it deploy e36361f
Test workers are now sized to 3/4 of the check cell's declared CPUs, so the check stops using more CPU than its cell allows: one worker per declared CPU averaged 16.1 of 16 cores and stopped main (#2161). This merge also brings in main's change #2260 for the same problem, and both limits now apply together.
- What this branch does:
workerCpus(inscripts/shards.ts) caps the CPU count atfloor(check.limits.cpus × 3/4)fromforge.json, never below 1. The memory bound also counts the cell's declaredmemoryMiB. - What #2260 added on
main:shardCountFromEnvreadsFORGE_CELL_CPUS, which the forge sets for a check, and caps the shard count at one less than that number. It still honours theFORGE_CHECK_SHARDSoverride. - The conflict: both sides changed the import list in
scripts/test-shards.ts. The script now importsworkerCpusandshardCountFromEnv, and passes theworkerCpusresult intoshardCountFromEnv. The plainshardCountimport is gone because nothing uses it. The two caps are separate, so the lower one wins.
No tests changed. The type check is clean, and test/shards.test.ts and test/check-tiers.test.ts pass (92 tests). I didn't run the full suite; the forge's check runs it on this push.
🤖 Generated with Claude Code
No user-facing surface here (check tooling only), so nothing to break on a phone or keyboard. workerCpus is pure and tested, including the clamps (machine, 1, undefined, 0), and a test pins that forge.json's own cell gets fewer workers than CPUs. The memory bound now counts the declared cell too. The security and both-doors items are not touched. Next, two things: (1) main already passes FORGE_CELL_CPUS (src/checks/service.ts:797) and shardCountFromEnv caps on it, so this branch's read of forge.json duplicates that signal. Fold workerCpus into the FORGE_CELL_CPUS path so the number comes from one place. (2) Add a test for the combined cap (workerCpus result plus FORGE_CELL_CPUS) so the "lower wins" claim in the pull is held by a test, not only by the description.
The full gate on land was red; the pull is open again. Red tests:
- what the open web expects > releases.json is the shipped releases, newest first, in the shape a child reads
- what the open web expects > releases.json is the shipped releases, newest first, in the shape a child reads [1.34ms] [shard 161]
The gate now gets the cell's CPU count from one place, FORGE_CELL_CPUS, and runs 3/4 that many workers. The 16-CPU check cell gets 12, not the 16 that averaged 16.1 cores and stopped main (#2161). This also fixes the red releases.json test, which broke on main itself.
- One source for the CPU count (@juniper 1):
scripts/test-shards.tsno longer readscheck.limits.cpusfromforge.json. It passesavailableParallelism()toshardCountFromEnv, andshardCountcaps onFORGE_CELL_CPUSviaworkerCpusinstead ofcpus − 1. Across all cell sizes,floor(3/4 × cpus)is never higher thancpus − 1, so it replaces that cap rather than adding a second one.forge.jsonis still read for the cell's declared memory: nothing passes that in the environment, and/sysmay not show it. - Test for the combined cap (@juniper 2): a new test in
test/shards.test.tspins that the lowest bound wins. For a 16-CPU cell on a 64-core host the result is 12. A smaller machine (8), less memory (4 GiB → 8) or fewer files (5) gives a lower count, andFORGE_CHECK_SHARDSstill wins over all of them. - One expectation in main's tests changed on purpose: the #2257 test expected
shardCount(80, 400, …, "16")to give 15. The new rule gives 12, and 15 workers still peak past 16 cores. The"4"→ 3 case and main's otherFORGE_CELL_CPUStests are unchanged. - The red test (@trevato): release 0.7.152 brought
releases.jsonto 101 entries, but a child reads at mostMAX_RELEASES(100). The test intest/standards.test.tswas wrong in assuming a child reads every entry. It now checks that the server sends the whole list, that a child reads the newestmin(n, MAX_RELEASES), and that the entries past 100 are in the same shape.MAX_RELEASESis now exported fromsrc/core/parent_releases.tsfor this; the endpoint's behaviour is unchanged.
The type check is clean, and the shards, check-tiers, gate-summary and standards suites pass (112 tests). I didn't run the full suite; the forge's check runs it on this push.
🤖 Generated with Claude Code
The test runner now gives the check 3/4 of its cell's CPU limit as workers, read only from FORGE_CELL_CPUS. On the 16-CPU check cell it runs 12 at once. With one worker per CPU, main's check averaged 16.1 of 16 cores over 30 s and the cell's watchdog in src/apps/contain.ts stopped it (#2161). This also fixes the releases.json test that went red on main at release 0.7.152.
The failed check ran on the previous head, 49ef6efe, and its only failure was that releases.json test. The fix was already in the last push (309a8679), so nothing new was pushed for this round, and that head's check is still queued. Here, the shards, check-tiers and standards suites pass (102 tests), as do the 37 tests that touch parent_releases.
What to look at:
- Worker count:
scripts/test-shards.tspassesavailableParallelism()toshardCountFromEnvand no longer reads the CPU count fromforge.json. Inscripts/shards.ts,shardCountnow caps onFORGE_CELL_CPUSthroughworkerCpusinstead ofcpus − 1.floor(3/4 × cpus)is never higher thancpus − 1, so it replaces that cap rather than adding a second one. - Memory:
forge.jsonis still read for the cell's declared memory, which nothing passes in the environment. It joins the cgroup limits inmemoryLimit. - New test for the combined cap: in
test/shards.test.ts, the lowest bound wins. A 16-CPU cell on a 64-core host gives 12; a smaller machine, less memory or fewer files gives less;FORGE_CHECK_SHARDSstill overrides everything. - One existing test changed on purpose: the #2257 test expected 15 workers for
FORGE_CELL_CPUS=16; it now expects 12, because 15 workers still peak past 16 cores. Its other expectations are unchanged. - The
releases.jsontest was wrong: it assumed a child reads every release, and release 0.7.152 made 101 of them while a child reads at mostMAX_RELEASES(100).test/standards.test.tsnow checks that the server sends the whole list, a child reads the newestmin(n, MAX_RELEASES), and the entries past 100 have the same shape.MAX_RELEASESis now exported fromsrc/core/parent_releases.ts; the endpoint itself is unchanged.
To try it: bun test test/shards.test.ts test/standards.test.ts. On the check cell, the gate summary shows 12 shards.
🤖 Generated with Claude Code
The test runner now gives the check 3/4 of its cell's CPU limit as workers, read only from FORGE_CELL_CPUS. On the 16-CPU check cell it runs 12 at once. With one worker per CPU, main's check averaged 16.1 of 16 cores over 30 s and the cell's watchdog in src/apps/contain.ts stopped it (#2161).
This push merges main again. Main had fixed the same releases.json test independently, and that is where the two conflicts were:
src/core/parent_releases.ts: both sides exportedMAX_RELEASES; main's doc comment on it is kept.test/standards.test.ts: main's assertion is kept, that a child reads the newestmin(n, MAX_RELEASES)while the record keeps every release. This branch's checks are kept too: the server sends the whole list, and the entries past the newest 100 have the same shape a child reads.
The type check is clean. The standards, shards, check-tiers and parent_releases suites pass (130 tests); I didn't run the full suite, which the forge's check runs on this push.
What to look at:
- Worker count:
scripts/test-shards.tspassesavailableParallelism()toshardCountFromEnvand no longer reads the CPU count fromforge.json. Inscripts/shards.ts,shardCountnow caps onFORGE_CELL_CPUSthroughworkerCpusinstead ofcpus − 1.floor(3/4 × cpus)is never higher thancpus − 1, so it replaces that cap rather than adding a second one. - Memory:
forge.jsonis still read for the cell's declared memory, which nothing passes in the environment. It joins the cgroup limits inmemoryLimit. - New test for the combined cap: in
test/shards.test.ts, the lowest bound wins. A 16-CPU cell on a 64-core host gives 12; a smaller machine, less memory or fewer files gives less;FORGE_CHECK_SHARDSstill overrides everything. - One existing test changed on purpose: the #2257 test expected 15 workers for
FORGE_CELL_CPUS=16; it now expects 12, because 15 workers still peak past 16 cores. Its other expectations are unchanged.
To try it: bun test test/shards.test.ts test/standards.test.ts. On the check cell, the gate summary shows 12 shards.
🤖 Generated with Claude Code
Approved. Check workers are now floor(3/4 × FORGE_CELL_CPUS), at least 1, so the 16-CPU cell runs 12 and stays under the watchdog. The change touches no served page or API, so nothing in the preview or the security list is affected. The #2257 expectation moving from 15 to 12 is deliberate and explained. The tests pin the combined cap (lowest bound wins), the FORGE_CHECK_SHARDS override, and that forge.json's cell gets fewer workers than CPUs. Two things I would do next: (1) the duplicated workerCpus(64,16)===12 assertions in test/shards.test.ts can collapse into one; (2) have the gate summary print the cell CPU limit beside the shard count, so a future 16.1-core stop is diagnosable from the log alone.
The gate's test runner now sizes itself to the check's limits as declared in
forge.json, not to the host's core count. Before, main's check was being stopped at 1aecf60 ("used 16.1 cores for 30s, over its limit of 16"), and every pull check queued behind it.Cause.
scripts/test-shards.tsran one worker per CPU thatavailableParallelism()reports. That is the host's count, not the limit of the cell the check runs in (the sandbox the forge gives each check). The cell's limit is enforced by a watchdog insrc/apps/contain.ts, which stops a check whose processes average more thancheck.limits.cpuscores over 30 s. Nothing in/procor/sysshows that limit. So the gate ran 16 or more workers against a 16-core limit. Each worker goes above one core at its peaks (Bun's own threads, git children, test forges it boots), so the 30 s average crossed 16.Change.
scripts/shards.ts: a new pureworkerCpus(machine, declared)caps workers atCELL_CPU_SHARE(3/4) of the declared CPUs. It never goes above the machine's count or below one, and with no declared limit it uses the machine's count. On the 16-CPU cell the gate now runs 12 workers at once.scripts/test-shards.ts: readscheck.limitsfromforge.jsonwith the forge's ownparseCheckand passes the CPUs throughworkerCpus. The declared memory (8 GiB) joins the cgroup limits thatmemoryLimitalready reads, because the same watchdog enforces it. At 512 MiB per worker that still allows 16, so it doesn't change the count today.scripts/gate-summary.ts: doc comments only. The summary's "N CPUs" is now the count the workers were sized to.FORGE_CHECK_SHARDSstill overrides all of this.Tests.
test/shards.test.tshas two new tests:forge.jsoncheck runs fewer workers than it declares CPUs.No existing test changed.
shardCountbehaves as before, and the existing "16 CPUs and 8 GiB run 16" case still holds for the function on its own. Type check is clean, andbun test test/shards.test.ts test/gate-summary.test.tspasses (55/55).Not done.
forge.jsonworks on the next push without the forge having to deploy first.To try it: the next full check's log should start
test-shards: N files, one bun test each, 12 at a time. The sentinel's mark isred:01m23vygyffx5hg2f5vm8r74srstaying off the degraded list for a day; 3/4 is my estimate of enough headroom, not a measured value.🤖 Generated with Claude Code
Closes #2161