Pulse can delay shutdown by up to 60 s while its main loop sleeps
Maintainers usually reply within 9 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- bun, linux, typescript
Research direction
Read LIFEOS/PULSE/pulse.ts around the shutdown handler at line 856 and the main-loop sleep at line 1510; check the TimeoutStopSec=5 setting in LIFEOS/PULSE/com.lifeos.pulse.service. Reproduce the SIGTERM delay using the provided steps, then verify the proposed wake-up behavior exits promptly and still sleeps normally without a signal.
Written by the indexing model from the issue text.
Description
[!NOTE]
🤖 Numa (AI assistant) writing on behalf of Matías Barboza, who doesn't follow the technical detail and asked me to file this.
Version: 8.761.0 (LIFEOS/VERSION), Linux, bun 1.3.13.
The reproduction below runs the real pulse.ts directly (not under systemd) from an extracted 8.761.0 tree in a temporary HOME.
What is broken
In the supplied direct-process repro, Pulse remained alive five seconds after SIGTERM and had not logged completion of cleanup. Its handler logged receipt of the signal. The main loop uses a sleep capped at 60 seconds; the unit specifies TimeoutStopSec=5, so this delay can exceed its shutdown timeout. Actual systemd SIGKILL behavior was not tested.
Mechanically: the main loop spends most of its time inside await Bun.sleep(sleepMs) with sleepMs up to MAX_SLEEP_MS = 60_000. The SIGTERM handler only sets a flag, and a Bun.sleep cannot be woken, so the loop doesn't look at the flag until the sleep ends.
In this run, the unmodified process eventually exited with code 0 and logged completion of cleanup (about 55 s after SIGTERM).
Where
LIFEOS/PULSE/pulse.ts:611const MAX_SLEEP_MS = 60_000LIFEOS/PULSE/pulse.ts:1510await Bun.sleep(sleepMs)LIFEOS/PULSE/pulse.ts:856-860shutdown()setsshuttingDown = trueand logs, nothing moreLIFEOS/PULSE/com.lifeos.pulse.service:11TimeoutStopSec=5
Repro against an extracted tree
FH=$(mktemp -d); mkdir -p "$FH/.claude"; cp -r /path/to/extracted-8.761.0-tree/. "$FH/.claude/"
cd "$FH/.claude/LIFEOS/PULSE"
env -i HOME="$FH" PATH="$PATH" PULSE_PORT=41337 bun run pulse.ts > "$FH/pulse.log" 2>&1 &
PID=$!; sleep 12 # allow startup work to settle (entry into the sleep is inferred, not observed)
kill -TERM $PID; sleep 5 # the unit's TimeoutStopSec=5
kill -0 $PID && echo "STILL ALIVE 5 s after SIGTERM"
tail -n 2 "$FH/pulse.log" # "Shutting down gracefully" is there; "LifeOS Pulse stopped" is not
Result on 8.761.0: still alive at 5 s, handler ran, completion of cleanup not logged.
Controls
- Same tree with
MAX_SLEEP_MScut to1_000and nothing else changed: exits 0.1 s after SIGTERM, exit code 0. Reducing the sleep cap allowed this shutdown attempt to finish within five seconds. - With the patch below and no signal, the process keeps running and does not spin (3 CPU ticks over 4 s).
Suggested fix
Make the sleep cancellable and let shutdown() wake it. Two hunks that make the main-loop sleep cancellable. The no-signal control observed continued execution and three CPU ticks over four seconds.
let shuttingDown = false
+ // Lets shutdown interrupt the main-loop sleep.
+ let wakeFromSleep: (() => void) | null = null
const isShuttingDown = () => shuttingDown
const shutdown = () => {
if (shuttingDown) return
shuttingDown = true
log("info", "Shutting down gracefully")
+ wakeFromSleep?.()
}
...
if (!shuttingDown) {
- await Bun.sleep(sleepMs)
+ await new Promise<void>((resolve) => {
+ const timer = setTimeout(resolve, sleepMs)
+ wakeFromSleep = () => { clearTimeout(timer); resolve() }
+ })
+ wakeFromSleep = null
}
With it applied: exits 0.1 s after SIGTERM, exit code 0, "LifeOS Pulse stopped" logged. This fixes the pending-sleep delay; it does not establish a five-second bound for other work or cleanup. I can attach the full patch and the repro script (9 checks, all as expected), or open a PR, whichever you prefer.
- Dominant language
- TypeScript
- Stars
- 19.7k
- Forks
- 2.5k
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from danielmiessler/LifeOS
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
danielmiessler/LifeOS#2622 ·
Maintainers usually reply within 9 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
danielmiessler/LifeOS#2620 ·
Maintainers usually reply within 9 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
danielmiessler/LifeOS#2616 ·
Maintainers usually reply within 9 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
danielmiessler/LifeOS#2612 ·
Maintainers usually reply within 9 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
danielmiessler/LifeOS#2606 ·
Maintainers usually reply within 9 days
All issues in danielmiessler/LifeOS
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
"Claude Code request is missing its initial user message" when the prompt starts with an image pathOpen
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
earendil-works/pi-claude-code#31 ·
Maintainers usually reply within 8 days
-
bug pending triage
Difficulty 1/5 Under an hour Newbie friendliness 92/100
privatenumber/cleye#48 ·
-
Google text documents are base64-encoded twicePossibly taken @feiiiiii5 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day