Tuning tman for Gradle & Maven
The Gradle daemon deliberately outlives your run — supervise around it, not through it.
Recommended starting caps
| cap | start at | what it answers |
|---|---|---|
stall | 15m | hang backstop — no output and no CPU/IO activity for this long |
max-time | 30m | wall-clock budget — the only cap that bounds a run that is working but will never finish |
max-mem | 8192 | RSS ceiling across the process tree |
max-parallel | 1 | slots per name-and-directory bucket |
These are starting points derived from how Gradle & Maven behaves, not measurements of your machine. Every one of them should be replaced by a number you took from your own runs — the last section on this page is how.
alias "test" {
command "./gradlew"
args "test" "--no-daemon"
max-time "30m"
max-mem 8192
max-parallel 1
}Gradle and Maven share a shape: a long, quiet dependency-resolution phase in front, a compile phase that saturates cores, and a JVM whose memory is governed by its own flags rather than by the operating system. Gradle adds one complication the others do not have.
The daemon is a process outside your tree
Gradle's default is to hand the work to a long-lived background daemon and have the client wait for it. Under supervision that inverts what you asked for: the process tman is watching is a thin client, the actual compile and test work happens in a process it never sees, and killing the client on a timeout leaves the daemon running exactly as before.
| what you want | how to get it |
|---|---|
| caps that actually bound the build | --no-daemon in the supervised alias, so the work happens inside the tree |
| daemon speed for interactive use | leave your own shell alone; supervise the agent's and CI's alias only |
| a daemon that does not accumulate | ./gradlew --stop in cleanup — tman will not reap it, because it is not a child of any run |
The reaper cannot help you here. It kills children whose runner died. A Gradle daemon is deliberately not a child of your run — it is designed to outlive it — so no amount of supervision reaches it. That is a good reason to prefer
--no-daemonfor automated runs and to keep the daemon for the shell you are sitting in front of.
`max-mem` versus `-Xmx`
The JVM's own heap limit fails with an OutOfMemoryError and a stack trace naming what was allocating; tman's max-mem kills the tree and returns 126. As with Go's -timeout, set the tool's own limit below tman's and let the informative one fire first. tman's ceiling has to clear the heap plus metaspace plus the compiler's own overhead — 8 GB against a 4 GB heap is a reasonable starting ratio.
Turn the starting point into a real number
Everything above is derived from how this tool behaves, not from your machine. The numbers that matter come from your own runs, and getting them takes one command:
tman status --json | jq '.runs[] | {name, exit, duration_ms}'Set max-time from the slowest successful run you are willing to wait for, with real headroom — a cap that fires on healthy work teaches everyone to raise the cap, and after a few rounds of that it bounds nothing. Set stall from the longest stretch the command legitimately produces no output and no CPU, then double it.
Exit 124, 125, and 126 are findings, not flakes. 124 is a wall-clock timeout, 125 a stall, 126 a resource cull. Widening the cap that fired is the right response only after you know why it fired — the first two times a suite trips
max-mem, the interesting question is what is holding the memory.
Frequently asked
Why does tman recommend --no-daemon for supervised Gradle builds?
Because with the daemon, the supervised process is a thin client and the real compile and test work happens in a background process outside the tree. Caps then bound the client rather than the build, and killing the client on a timeout leaves the daemon running. --no-daemon puts the work back inside the tree tman is watching.
Will tman's reaper clean up a leftover Gradle daemon?
No. The reaper kills children whose runner has died; the Gradle daemon is deliberately not a child of your run. Use `./gradlew --stop` in your cleanup step, and prefer --no-daemon for automated runs so the situation does not arise.