Skip to content
GitHubRSS

Dev servers as graph nodes: readiness instead of sleep

Some tasks do not finish. A dev server, a watcher, a database for the integration tests. Most runners either refuse to model them or model them as “run this and never wait,” which leaves the interesting problem to a sleep 5 in a shell script.

vx models them as persistent tasks and gives them the two things a graph node needs: a notion of ready, and a guaranteed end.

dev: {
exec: {
command: 'vite',
persistent: { readyWhen: 'Local:' },
timeout: 30_000,
},
},
e2e: {
dependsOn: ['dev'],
exec: { command: 'playwright test' },
},

readyWhen is a regex matched against the task’s output. The moment a line matches, the task is ready and its dependents are released; e2e starts against a server that is actually listening. The match also sees a trailing partial line, so a prompt with no newline (Listening on :3000) works.

exec.timeout bounds the wait. If the pattern never appears, vx kills the process and fails the task instead of hanging the run. A persistent task with no readyWhen is ready on spawn, which is right for a daemon nothing gates on.

No polling loop, no wait-on package, no guessed number of seconds. The server tells you when it is up, and you wrote down what it says.

When the run finishes, every persistent task is sent SIGTERM, given a grace period, then SIGKILLed if it is still there. Nothing is left listening on a port after vx run e2e returns, whether the tests passed, failed, timed out or you pressed Ctrl-C. That last case is its own post.

Readiness escalates the same way. A server that ignores the timeout’s SIGTERM gets SIGKILL after the grace, so a wedged process cannot hold the run open.

vx run dev with nothing depending on it is the other common case: you want the server in your terminal and you want vx to stay attached. When the requested tasks are persistent, vx stays in the foreground until one of them exits, then reports which one and stops the rest:

vx: web#dev exited with code 1; stopping 2 other persistent tasks

A crashed server makes vx exit 1.

A persistent task is pinned to this machine, and so is everything that depends on it: a remote worker cannot reach a port on your laptop, and the placement stage knows that without being told. A persistent task under a sandbox gets the same grants and walls as any other, but no violation report, because the report reads the trace after the child exits and a server exits only when the run tears it down.

Persistent tasks are not cached. They have no end state to store. Their dependents can be; e2e’s key includes dev’s key, so a config change to the server re-runs the tests.

The guide, with the readiness patterns for the common servers, is Dev & long-running tasks.