Skip to content

px-alive hits 'start operation timed out' during legitimate GPIO lease_wait park #287

Description

@adrianwedd

Observed (live robot, 2026-08-23, post-#286)

2e7b280d (#219 Track B, merged this morning) added a bounded Type=notify
startup contract for px-alive, with EXTEND_TIMEOUT_USEC heartbeats sent
before READY=1 specifically so the daemon can park indefinitely behind a
foreign GPIO lease (lease_wait) without the systemd watchdog killing it —
see CLAUDE.md's Idle-Alive Daemon section: "Do not add heartbeats inside
initialisation instead — that would keep the watchdog fed while wedged,
blinding it to the thing it exists to catch."

Tonight that contract was inconsistent. Journal excerpt (TimeoutStartUSec=1min):

19:53:03 px-alive[98434]: starting pid=98434 dry=False
19:53:03 px-alive[98434]: GPIO lease active (voice PID 96693) — parking in lease_wait
19:57:03 systemd[1]: px-alive.service: start operation timed out. Terminating.
19:57:03 px-alive[98434]: received signal 15 — shutting down
19:57:03 systemd[1]: px-alive.service: Failed with result 'timeout'.

19:57:28 px-alive[99696]: starting pid=99696 dry=False
19:57:28 px-alive[99696]: GPIO lease active (voice PID 96693) — parking in lease_wait
19:58:27 systemd[1]: px-alive.service: start operation timed out. Terminating.
19:58:27 px-alive[99696]: received signal 15 — shutting down
19:58:27 systemd[1]: px-alive.service: Failed with result 'timeout'.

Two consecutive restart attempts (restart counter 1→2→3) were killed by
systemd's start-timeout while legitimately parked waiting on
state/gpio_lease.json (owner: px-wake-listen's voice-turn PID 96693, a real,
actively-renewing lease, not stale). A third attempt at 19:58:57 succeeded and
ran cleanly for ~48 minutes. A later park (20:47:58, again behind the same
voice PID's lease) did not time out and was still parked several minutes in
when observed, so the failure is intermittent rather than universal — the
EXTEND_TIMEOUT_USEC heartbeat during park appears to not always be
sufficient/firing.

Why this matters

Each timeout-kill is currently masked by Restart=always + 10s +
StartLimitIntervalSec=0, so it self-heals and nothing user-visible breaks
today. But it means the #219 Track B contract ("bounded startup, 44.7s
typical") doesn't yet guarantee a legitimate park survives to READY under real
lease contention (a busy voice session doing several tool calls in a short
window) — it just guarantees the eventual restart loop recovers. That's a
weaker property than what the design doc/CLAUDE.md describe.

Suggested next step

Instrument or trace whether lease_wait's heartbeat-sending loop is actually
invoked on every poll iteration during a park (vs. only at park-entry), and
whether the interval between heartbeats can exceed systemd's effective
watchdog-extension window under load. Reproduce by holding a lease for >90s
while px-alive is (re)starting and watching for the timeout.

Not currently causing user-visible harm (self-heals via restart), so not
urgent — filed for tracking per #219's closeout review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions