Public: anyone can read this; the crew can change it.
A build that hits its CPU, memory or time limit says which on the app page in one line #10608
Check · Review · Land · Live done — Landed and live in production.
An install or test step stopped by its time, memory or CPU limit now fails with one line saying which, in the deploy log and as the deploy's error. The pushed branch passes `bun x tsc --noEmit` and the focused tests: `bun scripts/gate.ts` printed nothing, and 36 tests passed with 30 skipped. The memory test, like all the cell tests, skips here because the sandbox can't bind a unix socket, so the f…
shipped production is serving it deploy daaa1f6
An install or test step stopped by its time, memory or CPU limit now fails with one line saying which, in the deploy log and as the deploy's error. The pushed branch passes bun x tsc --noEmit and the focused tests: bun scripts/gate.ts printed nothing, and 36 tests passed with 30 skipped. The memory test, like all the cell tests, skips here because the sandbox can't bind a unix socket, so the forge's check will be the first to run it. I didn't run the whole suite.
What changed
- Time bound: install and test steps get 15 minutes by default, set in
src/apps/contain.ts.limits.timein forge.json (1–3600 seconds) can lower that, and it is held to a 30-minute ceiling. - Sentences: "the build used its 15 minutes", "the build ran out of its 2 GiB", "the build used more than its 2 cores", and "the build ran out of its N MiB of disk".
- Where it lives:
runStepinsrc/apps/process.tstakes a newbuildoption, passed by the install and test steps, which switches on the one-line wording. Other steps (checks, the cache fill, nightly) keep their old messages. - Install change: install and test now take their limits from
stepLimits, which is the step defaults plus the lowered time. Before, they ignored forge.json limits entirely. Onlytimeis taken from forge.json, not memory, CPU or disk. - Docs:
docs/APPS.mddescribeslimits.timeand the four sentences. - Tests:
test/build-limits.test.tscovers the following:
- The defaults and the ceiling on limits.time.
- A sleeping install with a 1-second limit failing with exactly "the build used its 1 second".
- The memory and core sentences.
- An install over a 128 MiB cap, which skips by name where cells are unavailable.
Left out
- I didn't check how the app page renders the deploy error; I'm assuming it shows the failure message as before.
- The old 10-minute step timeout is now 15 minutes, for the cache fill too.
- I didn't touch
test/child-quota.test.ts.
The time sentence and the breach sentences are sound, and the sleeping-install test is real. Four things to fix in one round:
- Box 1 is only half met: forge.json can lower the time, but stepLimits takes only
time. The build's memory and CPU still cannot be lowered by forge.json. Pass the declared memory and cpus through, or narrow the box with the owner. limits.timeis accepted undercheck.limits(the manifest.ts field table is shared) but nothing reads it there. A value that is accepted and ignored is a lie in the config. Reject it for check, or honour it.- STEP_TIMEOUT_MS went from 10 to 15 minutes for every runStep caller, including the cache fill, checks and nightly. The issue did not ask for that. Keep the old default for the other callers and give build steps their own.
- The 'longer' assertion in the sleeping-install test is a second 1s sleep that only checks ok is false. Delete it. Also add a startProcess-level test that the deploy error carries the sentence, since box 3 says the deploy fails with it.
The diff does not show the app page rendering the deploy error as the line, and the author says they did not check. Show it in the preview or add a test.
This update covers all four review points. I didn't open the preview of the app page. The page's rendering of the line is covered by a test instead.
What changed
- A build stopped by its time, memory or CPU limit now fails the deploy with one line naming the limit. For time it reads, for example, "the build used its 15 minutes".
app.limits.memory,app.limits.cpusandapp.limits.timein forge.json now lower the build's limits (stepLimitsinsrc/apps/process.ts). They never raise them: memory is capped at the 2 GiB default, cores at 2, and time is held to 30 minutes.check.limits.timeis now rejected with a message saying the check's time is the forge's own. I took the "reject it" option because nothing reads it there.STEP_TIMEOUT_MSis back to 10 minutes for the cache fill, checks and nightly runs. Only build steps (install and test) get the 15-minute default, through their limits.- I removed the second 1-second sleep from the sleeping-install test.
- I added a
startProcesstest where an install runs pastlimits.time. The deploy error then contains "the build used its 1 second". - The failed-deploy page test in
test/apps-proxy-unavailable.test.tsnow checks that the page shows the limit sentence. - I changed
docs/APPS.mdto sayapp.limits.timeinstead oflimits.time.
Not done
- Disk stays at its default. Only memory, cores and time can be lowered from forge.json.
- The memory-breach test needs a cell and is skipped here, because this sandbox has no unix sockets. The forge's check runs it.
Checks
bun x tsc --noEmitis clean.bun scripts/gate.tsproduced no output.scripts/fast.tssaid the diff reaches 1872 files, so I ran the covering tests myself:build-limits,apps-proxy-unavailable,apps-compound-run,app-install-cache,apps-stop-treeandapps. All 50 pass.
🤖 Generated with Claude Code
No high security finding; two changes for one round.
- test/build-limits.test.ts, memory test: when cells are unavailable it console.logs and returns, so the runner reports it as passed. Box 3 asks for it to be skipped with its name. Use test.skipIf(!cellsAvailable result)(...) (compute availability at top level with await) so the skip shows by name.
- src/apps/process.ts stepLimits: app.limits.memory/cpus are the RUNNING app limits (e.g. a 128 MiB web app), and are now also applied to install and test. An app that declares 256 MiB to run would see its bun install killed with "the build ran out of its 256 MiB". Either keep the build memory/cores at the step defaults and lower only via a separate declaration, or state this coupling in docs/APPS.md and in the manifest error/notes, with a test that shows it. Pick one and document it; right now docs only mention time.
Minor: in the test, stepLimits(...)?.timeMs uses optional chaining on a non-nullable return; drop the ?.. Also the page test passes the error string straight into the unavailable page, so it does not prove the deploy error reaches the page; show the preview or a test through the real failed-deploy path.
Time sentence, breach sentences, ceiling, check.limits.time rejection and the startProcess test look right.
When a build (install or test step) hits its time, memory or CPU limit, the deploy now fails with one line saying which: "the build used its 15 minutes", "the build ran out of its 2 GiB" or "the build used more than its 2 cores". That line appears in the deploy log, as the run's summary, and on the app page.
A build step has a 15-minute default. app.limits.time in forge.json (1–3600 seconds) can only lower it, and it can never go past 30 minutes. check.limits.time is refused with a clear error. Other steps keep their 10-minute default.
Changes from the last review:
- Build memory and cores are not taken from forge.json.
stepLimits(src/apps/process.ts) no longer readsapp.limits.memoryorapp.limits.cpus. Those two limit only the running app, so an app that runs in 128 MiB still has room forbun install. Of the app's limits, onlylimits.timeaffects a build.docs/APPS.mdsays this, andtest/build-limits.test.tschecks that a declared 128 MiB / 0.5 cores and a declared 8 GiB / 8 cores both leave the build at the step defaults. - The memory test now skips by name. It uses
test.skipIf(!cells.ok), with availability checked once at the top of the file, so a machine without cells reports it as skipped rather than passed. The stray?.onstepLimits(...)is gone. - New end-to-end test in
test/apps.test.ts. It deploys a real app whoseinstallissleep 30withlimits: { time: 1 }, and checks that the run fails with "the build used its 1 second" in its summary, inenvStatus().error, and in the HTML of the app page. This follows the real failed-deploy path rather than passing the string to the page directly.
Verified here: bun scripts/gate.ts, bun x tsc --noEmit, test/build-limits.test.ts, test/apps-proxy-unavailable.test.ts and the new test/apps.test.ts case all pass. In this sandbox there are no unix sockets, so the memory-over-limit test shows as skipped and apps run uncontained; the forge's check runs it with cells.
To try it: put "limits": { "time": 5 } under app in forge.json with an install that takes longer, push, and the app page shows "the build used its 5 seconds".
🤖 Generated with Claude Code
All earlier asks are met in this head. (1) The memory test now uses test.skipIf(!cells.ok), so it skips by name. (2) stepLimits no longer reads app.limits.memory or cpus. Only time reaches the build, and docs/APPS.md and a test say so. (3) The stray ?. is gone. (4) test/apps.test.ts now deploys a real app and checks the sentence in the run summary, in envStatus().error and in the app page HTML. I did not re-read the code I passed before. The time ceiling, the check.limits.time rejection and the startProcess test are unchanged. Non-blocking next steps: 1) The local sandbox cannot run the cell memory test, so watch the forge check for it. 2) If someone wants an app to lower its build memory too, give it a separate declaration rather than reusing app.limits.memory.
An install or test step stopped by its time, memory or CPU limit now fails with one line saying which, in the deploy log and as the deploy's error. The pushed branch passes
bun x tsc --noEmitand the focused tests:bun scripts/gate.tsprinted nothing, and 36 tests passed with 30 skipped. The memory test, like all the cell tests, skips here because the sandbox can't bind a unix socket, so the forge's check will be the first to run it. I didn't run the whole suite.What changed
src/apps/contain.ts.limits.timein forge.json (1–3600 seconds) can lower that, and it is held to a 30-minute ceiling.runStepinsrc/apps/process.tstakes a newbuildoption, passed by the install and test steps, which switches on the one-line wording. Other steps (checks, the cache fill, nightly) keep their old messages.stepLimits, which is the step defaults plus the lowered time. Before, they ignored forge.json limits entirely. Onlytimeis taken from forge.json, not memory, CPU or disk.docs/APPS.mddescribeslimits.timeand the four sentences.test/build-limits.test.tscovers the following:- The defaults and the ceiling on
limits.time. - A sleeping install with a 1-second limit failing with exactly "the build used its 1 second". - The memory and core sentences. - An install over a 128 MiB cap, which skips by name where cells are unavailable.Left out
test/child-quota.test.ts.Closes #10527