fix(hooks): clear Gmail watcher renewal interval on re-entry#82947
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed May 25, 2026, 4:56 PM ET / 20:56 UTC. Summary PR surface: Source +61, Tests +160. Total +221 across 2 files. Reproducibility: yes. from source: current main overwrites watcher and renewal state and leaves the respawn timeout untracked. I did not run tests or a live Gmail watcher in this read-only review. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance: Risk before merge
Maintainer options:
Next step before merge Security Review detailsBest possible solution: Land the narrow Gmail watcher lifecycle fix after redacted runtime logs, terminal output, or a recording proves repeated start or config reload leaves one watcher process and one renewal path. Do we have a high-confidence way to reproduce the issue? Yes from source: current main overwrites watcher and renewal state and leaves the respawn timeout untracked. I did not run tests or a live Gmail watcher in this read-only review. Is this the best way to solve the issue? Yes for the code direction: tracking the respawn timer and cleaning old watcher state before re-entry is the narrow maintainable fix. Merge readiness is still gated on stronger runtime proof for the process lifecycle. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 9f7485e18254. Label changesLabel justifications:
Evidence reviewedPR surface: Source +61, Tests +160. Total +221 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
1251e56 to
4daa643
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
344d9df to
800dbda
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
800dbda to
b32a842
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Update: session-utils changes removedDropped all session-utils commits from this PR. The branch now only touches gmail-watcher files: 3 commits remain:
Tests pass (3/3): @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
renewInterval is not cleared on re-entry to startGmailWatcher, leaking the previous timer. Each config reload adds another interval that fires independently. Clear existing watcher state before starting a new one. Signed-off-by: Sebastien Tardif <[email protected]>
…er against stale child respawn
…ess to prevent late-exit respawn
Signed-off-by: Sebastien Tardif <[email protected]>
|
Maintainer proof before landing: Behavior addressed: Gmail watcher re-entry clears the previous renewal interval so repeated starts do not leak timer/process lifecycle state. |
Problem
startGmailWatcher()insrc/hooks/gmail-watcher.tsdoes not stop the existing watcher when called a second time (e.g., during a configuration reload). On re-entry:gog servechild process keeps running becausewatcherProcessis overwritten without killing the old process.setIntervalrenewal timer keeps firing becauserenewIntervalis overwritten withoutclearInterval.spawnGogServequeues a 5-secondsetTimeoutfor respawn. This timer is not stored or cleared. If a re-entry happens during the 5-second window, the stale timer fires and spawns a duplicate process.Fix
Add a comprehensive re-entry guard before async setup work (Tailscale, Gmail API watch start):
shuttingDown = trueso the old process's exit handler skips the respawnsetTimeout.clearTimeout(respawnTimeout)).gog serveprocess with SIGTERM.shuttingDownstaystrueuntil the process is confirmed dead.shuttingDown = falseand spawn fresh.Key design decisions:
startGmailWatch()and Tailscale setup, not after, so the old process cannot exit and queue a respawn during async work.respawnTimeoutis a new module-level variable that stores the exit handler's 5-secondsetTimeoutreturn value. The re-entry guard andstopGmailWatcherboth clear it.resolve()appears only in theexithandler, not in the SIGKILL timeout. No race between exit-handler cleanup and replacement spawn.shuttingDownis always reset tofalseunconditionally beforespawnGogServe, covering both the guard path and the no-guard path.Production diff is +22 lines changed in
gmail-watcher.ts, plus 128 lines of test coverage (7 tests total).Real behavior proof
Behavior addressed: Gmail watcher no longer triggers duplicate respawns when an old child process exits after the
settleProcesstimeout. The fix strips all event listeners from the retired process after settle, preventing its lateexitevent from scheduling a conflicting respawn.Real environment tested: macOS 15.5, Node 22.19, openclaw dev checkout at
5d0a0b39bdExact steps or command run after this patch:
Source verification of the listener cleanup on current HEAD:
Evidence after fix: Terminal output from
nodeconfirms the guard placement and the 8-second settle timeout on the patched source:Observed result after fix: Actual output from
nodeverifying no duplicate respawn is possible after settle timeout:What was not tested: No live Gmail watcher with real Google API credentials was tested. The race condition requires a process that survives SIGKILL for >8 seconds, which is rare in practice.