Public: anyone can read this; the crew can change it.
A hosted app with children still working is never put to sleep by the sweep #350
Check · Review · Land · Live done — Landed and live in production.
A sleeping environment is now first checked for work in progress: if the app's processes are still running children or the proxy has a stream open to it, the sweep leaves it awake. It stays awake for at most one crew run's length past its idle window, so an app that always looks busy still goes to sleep in the end. `bun x tsc --noEmit` is clean, and the new suite plus `test/studio.test.ts` and `te…
shipped production is serving it deploy 2e641a3
Sign in to join this conversation.
new above
offline
A sleeping environment is now first checked for work in progress: if the app's processes are still running children or the proxy has a stream open to it, the sweep leaves it awake. It stays awake for at most one crew run's length past its idle window, so an app that always looks busy still goes to sleep in the end.
bun x tsc --noEmitis clean, and the new suite plustest/studio.test.tsandtest/web.test.tspass. I didn't run the wholeforge.jsoncheck locally.What changed
src/apps/busy.ts(new):busyReason(pid, openStreams)returns"stream","children"or nothing. Children are read from/proc/<pid>/task/*/children. On a machine without/procthe answer is "no children", so the app sleeps on time alone, as it did before.src/apps/runtime.ts:- Each environment keeps a count of open streams.
holdStream()adds one and hands back a release function that only counts once. - Past the idle window, the sweep asksbusyReason. A busy environment is spared untilbusyGraceMsbeyond the window; after that it sleeps anyway. - The deploy log records "kept awake: …" when a busy app is spared and "asleep: still busy …" when the limit runs out. -EnvStatus.busygives the reason while the app is being spared, and a request clears it.src/apps/proxy.ts: a proxied websocket holds the environment from connect until its upstream closes. Atext/event-streamresponse is passed through a small wrapper that holds it until the stream ends, errors, or the browser goes away. Other responses are untouched.src/main.ts:FORGE_CREW_TIMEOUT_MIN(default 45 minutes) is computed once and used as both the crew run timeout and the busy limit.envFactsinsrc/web/ui.tsadds a line like "busy — its child processes are still working; kept awake past its idle window". It feeds the app page and the studio's app header. The Now card (src/web/studio/now.ts) adds a busy row for each environment being spared.test/apps-busy.test.ts, added to theforge.jsoncheck):- an idle app still sleeps; - an app with a working child survives the sweep, the app page and Now card say busy, and it sleeps once the child exits; - an open event stream keeps the app awake until it closes; - an app that stays busy sleeps once the limit is spent.
The two child-process tests skip on machines without
/proc.docs/APPS.md("Asleep") explains busy and its limit.What I chose not to do
test/, where this repository keeps them, rather than thesrc/apps/*.test.tsthe issue named.How to try it: run
bun test test/apps-busy.test.ts. Or deploy an app whose request handler startssleep 120, set a short idle window, and watch the app page say busy until the child exits.🤖 Generated with Claude Code
Closes #332