Deploy checks
Every `runlot deploy` reads your own code for patterns that work on a laptop and hurt under traffic — collections that only grow, request data in module variables, undisposed RPC results — and tells you where they are and how to fix them. They never stop a deploy.
An app on Runlot is one process that serves every request, on one JavaScript thread, inside a memory limit. Code that is fine for one person clicking around can grow without bound or block everyone once real traffic arrives. runlot deploy reads your code before it uploads and points at those places.
Checks: 2 warnings (they do not stop the deploy)
WARN module-cache-unbounded src/sessions.ts:4
`sessions` is a module-level Map that is added to per call and never emptied.
Why: One app process serves every request until it restarts, so a collection at module scope keeps every entry it was ever given. The app is stopped when it reaches its memory limit (256 MiB unless raised).
Fix: Bound it: delete entries when they are done, cap its size and evict the oldest, or keep per-request data inside the handler.
WARN rpc-result-not-disposed src/rooms.ts:18
The result of stub.snapshot() is kept in a variable and never disposed.
Why: An RPC result that is an object keeps its call open until it is disposed. ...
Fix: Declare it with `using` (`using result = await stub.method()`), or call `result[Symbol.dispose]()` when you are done with it.
Silence a line with `// runlot-ignore <rule>` on it or the line above.Each finding names the rule, the file and line in your source (TypeScript included), what it saw, which limit it runs into, and how to fix it. The findings are stored with the deployment: the deployment's details in the dashboard show them, and runlot health lists the live deployment's.
What is checked
| Rule | What it finds | Why it matters on Runlot |
|---|---|---|
rpc-result-not-disposed | A Durable Object RPC result kept in a variable and used as an object, or an env.db.session(), with no using, dispose() or close() | The call stays open until it is disposed. In a request that logs "An RPC result was not disposed properly"; in a Durable Object it lasts as long as the object, so memory grows with every call |
module-cache-unbounded | A module-level Map, Set, array or object that a handler adds to, keyed by request data, and that nothing ever deletes from or checks the size of | One process serves every request until it restarts; the collection keeps everything until the app reaches its memory limit and is stopped |
module-state-per-request | A module-level let set from the request's data inside a handler | Requests run in the same process and interleave at every await, so the next request reads another user's value |
interval-in-request | setInterval called in a function and never cleared | The timers multiply with traffic and take the thread from requests. Use a scheduled run for periodic work |
time-loop-never-ends | A loop that waits for Date.now() or performance.now() to move without awaiting | The clock does not move while JavaScript runs, so the loop spins until the CPU limit stops the request — and every other request waits meanwhile |
sync-cpu-in-request | hashSync, compareSync, pbkdf2Sync, scryptSync, gzipSync and similar blocking calls in a handler | While the call computes, every other request of the app waits |
bundle-size | A worker that is at least a sixteenth of the app's memory limit (16 MiB at 256 MiB) | The whole worker is held in memory before the first request |
source-map-size | A source map too large to ship (over 32 MiB) | Errors and profiles of that deployment show bundled line numbers |
Only your own files are read — never node_modules. A library's internal cache is the library's to bound, and you could not change it.
A Node.js built-in module that your compatibilityDate does not provide is the one check that does stop a deploy, because such a worker cannot start at all; see Bundle.
When a finding is wrong
The checks look for patterns, and your code may already bound what they cannot see — a memo whose keys come from a fixed list, a session closed in another function. Silence that one line:
// runlot-ignore module-cache-unbounded
const byRoute = new Map<string, RegExp>();// runlot-ignore on its own silences every rule on that line. A comment on the line itself works too.
Limits of the checks
- Next.js: the worker is built from Next's compiled output, which does not map back to your files, so a Next.js deploy gets the size checks only. The deploy output says so.
- The checks read one file at a time with a few hops of context. They will miss a leak that spans files, and they do not run your code. The runtime side — memory climbing toward the limit, the RPC warning in the logs, CPU per request — is in Health checks.
- A deploy made without the CLI (Studio's Publish, an older CLI) is shown as "not checked", not as "no findings".
Reading them later
runlot health # the live deployment's findings, under the capacity risks
runlot health --json # staticChecks.report, for an agent or CIIn the dashboard, open Deployments and expand a deployment.