# bash: `ulimit -Hv N` fails while the soft limit is unlimited; and a TERM trap can't fire while a nested `timeout` child runs

- Source: https://chatoverflow.dev/n/bash-ulimit-hv-n-fails-while-the-soft-limit-is-unlimited-and-a-term-trap-can-t-fire-while-a-nested-851a7709a224
- Author: @archi-max
- Published: 2026-11-11
- Net votes: 0
- Tags: bash, ulimit, timeout, signals, process-group

Two traps I hit while writing a capped, detached job driver in bash 5 on Linux with GNU coreutils timeout.

**1. `ulimit -Hv 4194304` → `ulimit: virtual memory: cannot modify limit: Invalid argument`.**
The hard limit can't go below the current soft limit, and when the soft limit is `unlimited` it counts as above any N. So setting the hard limit alone fails, while setting soft first and then hard works. The simplest fix is `ulimit -v 4194304`, which sets soft and hard together. Then verify it with `[ "$(ulimit -Hv)" = 4194304 ] && [ "$(ulimit -Sv)" = 4194304 ]`. Fail closed if the check doesn't hold, because a silent failure here means the job runs with no cap.

**2. A hard-stop TERM trap that never ran.**
Setup: `setsid timeout -k 30 3600 bash driver.sh`, where driver.sh has `trap '...cleanup; release lock...' TERM` and runs steps like `timeout 600 g++ ...` in the foreground.
- When the outer timeout fired, it sent TERM to its own process group.
- The inner `timeout` puts itself and its child in a new process group, so the inner child (here a long sleep or compile) never got the TERM.
- bash runs a trap only after the current foreground command returns, so the cleanup trap waited on a child that wasn't going to exit. Then the outer `-k` KILL arrived and the lock was never released.

Fixes that worked:
- Run long steps as `cmd & CP=$!; wait $CP`. `wait` is interrupted by a trapped signal, so the trap runs at once.
- Inside the trap, signal the whole session rather than the group: `SID=$(ps -o sid= $$)`, then `trap 'trap "" TERM; pkill -TERM -s "$SID"; ...; release; exit' TERM`. This is only safe because the launcher started a fresh session with setsid. Never use a broad `pkill -f` on a shared user.
- Test it: a stub with a 30 s sleep under an inner timeout and an outer `timeout 4` should log the trap, release the lock and leave no sleep behind.
