fix(gateway): alert operator when hermes-home auto-pull is wedged by a dirty live tree - #45
Conversation
|
Warning Review limit reached
More reviews will be available in 27 minutes and 18 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📥 CommitsReviewing files that changed from the base of the PR and between 8fbc23ce49d6458106488da80d5a900c748cafca and cdbf2dc. 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughWhen the Hermes home auto-puller finds the checkout is behind ChangesWedged-state alerting for Hermes home puller
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
1 |
First entries
tests/gateway/test_hermes_home_puller.py:425: [unresolved-attribute] unresolved-attribute: Attribute `_notifier` is not defined on `None` in union `HermesHomePuller | None`
✅ Fixed issues: none
Unchanged: 5053 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@gateway/hermes_home_puller.py`:
- Around line 213-226: The user-facing log and alert strings in
hermes_home_puller.py currently hardcode "~/.hermes"; replace those literals
with the profile-aware display_hermes_home() from hermes_constants so messages
show the actual HERMES_HOME location. Locate the logger.info and logger.warning
calls (and the similar messages around the later block at lines ~306-312) and
interpolate or format display_hermes_home() into the logged strings instead of
the "~/.hermes" literal, keeping the rest of the message unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3b6afebd-f613-4d88-8b6c-8a7deea548fe
📥 Commits
Reviewing files that changed from the base of the PR and between 62a2d5886d1b0dd0b76d3589066dd560dd03df3f and 06b68c491cd7e06962e30c913bd26bfc5cefb40c.
📒 Files selected for processing (3)
gateway/hermes_home_puller.pygateway/run.pytests/gateway/test_hermes_home_puller.py
|
auto-review: APPROVED -> human_review (awaiting Sahil's merge + approve). Matrix checks (U1-U6, C1-C5)
Code-quality judgment (role-reviewer)Verdict: APPROVED. No blockers.
Caveat for human merge -- CI shard failures are PRE-EXISTING and UNRELATED
PR #45 modifies only Recommend: merge is safe w.r.t. this PR; suggest a separate card to fix the |
06b68c4 to
8fbc23c
Compare
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
8fbc23c to
101080d
Compare
…a dirty live tree The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests.
101080d to
cdbf2dc
Compare
|
auto-review: approved, awaiting human merge + kanban_approve. Matrix checks (U1–U6, C1–C6): all pass.
Code-quality judgment (role-reviewer): APPROVED. Single commit, clean rebase onto current main, black-box tests at public boundaries, dependencies injected (activity-state dict / notifier+clock), defensive guards sit on optional liveness/result paths (not required-value fallbacks). |
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…artbeat enforcement (#46) test_dispatch_once_stale_disabled_when_timeout_zero stored os.getpid() as worker_pid with a 5h-old started_at and no heartbeat, then called dispatch_once(stale_timeout_seconds=0). dispatch_once runs enforce_missing_heartbeat independently of stale_timeout_seconds, which os.kill(SIGTERM)'d the stored PID = the pytest process itself, killing pytest before it printed its summary (raw RC=143). The parallel harness then scraped 0 passed/0 failed and bucketed the file as 'no tests ran', turning test(4) red on #43/#44/#45 — broken-main from the upstream rebase. Fix: set a recent last_heartbeat_at on the run so enforce_missing_heartbeat skips the task. This test isolates STALE detection, not heartbeat enforcement. Also hardened test_enforce_max_runtime_integrates_with_dispatch (same os.getpid() footgun on its real-os.kill dispatch_once call). Test-only change. Verified: raw pytest RC=0, 166 passed 1 skipped. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs #54-#58, ~5 merges, only noticed when a cron broke). - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
…a dirty live tree (#45) The hourly ~/.hermes auto-pull silently DEBUG-skipped on a dirty working tree. The live tree is almost always dirty (continuous skill-edit churn), so incoming merges piled up undelivered on disk with zero alerting (PRs - _pull_once now counts commits-behind BEFORE the clean check so it can distinguish the WEDGED state (on main, behind>0, dirty) from up-to-date. Wedged invokes an optional notify callback; clean+behind still fast-forwards; up-to-date and feature-branch stay silent DEBUG. - HermesHomePuller takes a notifier + throttles wedged alerts (default 6h, tracked via _last_alert_ts) so the operator isn't paged hourly. The throttle clock only advances once the notifier actually accepts the message, so a failed send retries next tick. - GatewayRunner gains _send_operator_alert (reuses the home-channel adapter.send surface used by shutdown/startup notifications) and make_operator_alert_callback (thread-safe sink via safe_schedule_threadsafe). start_gateway threads the callback into start_hermes_home_puller. - Tests: wedged fires once then suppressed in-window then again after the window elapses; clean+behind fast-forwards no alert; up-to-date no alert no pull; feature branch no alert; notifier-exception non-fatal + no throttle advance. 30 -> 39 tests. Co-authored-by: Sahil (AI) <266772320+sahilm-ai@users.noreply.github.com>
Problem
gateway/hermes_home_puller.pyfast-forwards the live~/.hermescheckout hourly._pull_once()previously didbranch != main → skip, thennot _is_clean() → DEBUG skip (silent), then counted behind. The live tree is almost always dirty (continuous curator/worker skill-edit churn), so the dirty-skip path fired constantly and the auto-pull never landed incoming merges. PRs #54–#58 (~5 merges) piled up undelivered on disk with zero alerting — only noticed when a cron broke. t_1adf6618 manually reconciled them, but the failure recurs within days.This is candidate (1) of t_db5a6d17.
Change
Distinguish wedged from up-to-date.
_pull_oncenow counts_commits_behind()before the clean check. State machine:main→ silent DEBUG skipmain, behind==0 → silent DEBUG skip (up-to-date)main, behind>0, clean → fast-forwardmain, behind>0, dirty → WEDGED: log WARNING + invoke optionalnotify(behind)callbackNotification surface (reused, not invented).
GatewayRunner._send_operator_alert(message)pushes to every connected home channel viaadapter.send(...)with_thread_metadata_for_target(...)— the exact same home-channel delivery path the shutdown/startup notifications use.make_operator_alert_callback(loop)wraps it in a thread-safeCallable[[str], None]that schedules onto the gateway event loop viasafe_schedule_threadsafe(leak-safe on loop-close races).start_gatewaythreads this callback intostart_hermes_home_puller(notifier=...), the same way the cron ticker / code-watcher receive their gateway handles.Throttle.
HermesHomePullertracks_last_alert_ts(monotonic) and suppresses repeat wedged-alerts withinalert_throttle_secs(default 6h). The throttle clock only advances once the notifier accepts the message — a failed send retries on the next tick instead of being silently swallowed for 6h.Silent paths preserved. Up-to-date and feature-branch stay DEBUG-only. Only
main + behind>0 + dirtypages the operator.Scope note (fix(kanban): stop self-deleting worker scratch workspace on complete_task #5 in the card): branch != main is left as the existing silent DEBUG skip — a feature branch on the live checkout is deliberate operator intent, not a wedged auto-pull, so it doesn't warrant a page. The primary target (dirty+behind on main) is covered.
Out of scope (per card)
live_checkout_hygiene_watchdog.shcron backstop is separate; this PR is the in-process gateway auto-pull alert.Tests
tests/gateway/test_hermes_home_puller.pyextended 30 → 39 tests, all green viascripts/run_tests.sh:start_hermes_home_puller(notifier=...)threads the callback onto the instanceSummary by CodeRabbit