Skip to content

fix: guard against stale EXECUTING schedules from Netro cloud#53

Merged
simons-plugins merged 2 commits into
mainfrom
fix/stuck-executing-schedule
Apr 26, 2026
Merged

fix: guard against stale EXECUTING schedules from Netro cloud#53
simons-plugins merged 2 commits into
mainfrom
fix/stuck-executing-schedule

Conversation

@simons-plugins
Copy link
Copy Markdown
Owner

@simons-plugins simons-plugins commented Apr 22, 2026

Summary

  • Fix a stuck isIrrigating: True (and controller activeZone) when Netro cloud leaves a completed schedule marked EXECUTING after its end_time has passed.
  • Seen today on a Pixie (Front Garden Window Bed): a MANUAL run from 2026-04-19 at 19:07:05 was still flagged EXECUTING three days later. The HTML dashboard and Indigo both showed the zone as watering indefinitely.
  • Add SprinklerHandler._schedule_actually_executing() which requires status == "EXECUTING" AND (end_time missing OR in the future). Wire into both process_schedules (controller state) and process_zone_schedules (zone state). A stale EXECUTING on the zone's own history is demoted to lastWatering so the record still shows when the run actually happened.
  • Fall through to trusting status when end_time can't be verified, so a genuinely-running manual job without end_time still registers as active.

Test plan

  • pytest — 443/443 pass (436 existing + 7 new in TestStuckExecutingSchedule + SprinklerHandler stale-executing cases)
  • Existing V2/V1 fixture EXECUTING entries had past end_times — bumped to 2099 / year-4096 so they continue to exercise the genuine-running path
  • After release, verify Front Garden Window Bed - Front Garden (id 1939528366) flips isIrrigating to False within one poll cycle on jarvis

Version

2026.4.42026.4.5 (patch — internal reliability, no user-visible behavior change except unsticking the flag)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where schedules marked as "EXECUTING" would persist even after completion. The plugin now accurately detects when an executing schedule has finished based on its end time and properly updates the schedule status accordingly.
  • Chores

    • Version updated to 2026.4.5

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@simons-plugins has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 4 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 4 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 355f5593-5712-4a23-a1dd-c177cf9a8152

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd5c62 and 74dadba.

📒 Files selected for processing (5)
  • Netro Sprinklers.indigoPlugin/Contents/Info.plist
  • Netro Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py
  • tests/conftest.py
  • tests/test_device_handlers.py
  • tests/test_zone_handler.py
📝 Walkthrough

Walkthrough

Version increment and enhanced schedule processing logic to detect and ignore stale "EXECUTING" schedules that have already completed. Added validation checks comparing schedule end times against current time, with comprehensive test coverage for both SprinklerHandler and ZoneHandler flows across API versions.

Changes

Cohort / File(s) Summary
Plugin Configuration
Netro Sprinklers.indigoPlugin/Contents/Info.plist
Version bump from 2026.4.4 to 2026.4.5.
Schedule Execution Logic
Netro Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py
Added _schedule_actually_executing() method to validate whether "EXECUTING" schedules are still running by comparing end_time against current time. Enhanced _parse_schedule_sort_key() error handling with debug logging. Updated process_schedules() and process_zone_schedules() to detect and ignore stale "EXECUTING" entries while maintaining proper UI state transitions.
Test Fixtures
tests/conftest.py, tests/test_zone_handler.py
Updated schedule timestamp fixture data from 2026-04-07 to 2099-04-07 and adjusted millisecond timestamps for zone schedule tests.
Unit Tests
tests/test_device_handlers.py, tests/test_zone_handler.py
Added comprehensive regression tests covering stale "EXECUTING" schedules with past end times (both V1 numeric and V2 ISO formats), active "EXECUTING" with future end times, schedule selection logic, boundary conditions, and error handling for unparseable timestamps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A sprinkler said "I'm still running true!"
But time had passed, the work was through—
Now stale schedules fade from view,
With tests that validate what's due. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: guard against stale EXECUTING schedules from Netro cloud' directly and accurately summarizes the primary change: fixing a condition where completed schedules remain marked as EXECUTING.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/stuck-executing-schedule

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
Netro Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py (2)

237-245: Dead ternary — both branches return float(raw_value).

The V1 branch collapses to the same expression on both sides of the conditional:

return float(raw_value) if isinstance(raw_value, str) else float(raw_value)

This is a no-op isinstance check — likely a leftover from when the string path did something different. Simplify:

♻️ Proposed fix
-            else:
-                # V1: Millisecond timestamp (may be string)
-                return float(raw_value) if isinstance(raw_value, str) else float(raw_value)
+            else:
+                # V1: Millisecond timestamp (may be string)
+                return float(raw_value)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Netro` Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py
around lines 237 - 245, The V1 branch contains a dead ternary that returns
float(raw_value) in both cases; replace the conditional expression with a single
return float(raw_value) to simplify the timestamp parsing logic (leave the
surrounding try/except and the debug log referencing raw_value and api_version
unchanged). Locate the timestamp parsing code that checks api version V1 and
uses raw_value (the line currently: return float(raw_value) if
isinstance(raw_value, str) else float(raw_value)) and change it to a single
return of float(raw_value).

178-181: Duplicate debug-log string across SprinklerHandler and ZoneHandler.

The same message — "Ignoring stale EXECUTING schedule id=%s zone=%s end_time=%s" — is emitted verbatim from both SprinklerHandler.process_schedules (lines 178-181) and ZoneHandler.process_zone_schedules (lines 673-676). Since _schedule_actually_executing is the single source of truth for the decision, a small helper (or having _schedule_actually_executing itself emit the debug line at decision time) would remove the duplication and keep the log format consistent if it ever changes.

Also applies to: 673-676

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Netro` Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py
around lines 178 - 181, Both SprinklerHandler.process_schedules and
ZoneHandler.process_zone_schedules duplicate the same debug message when a
schedule is considered stale; centralize that logging by moving the debug emit
into the single decision point _schedule_actually_executing (or create a small
helper called e.g. log_stale_executing_schedule called by both callers). Update
_schedule_actually_executing (or the new helper) to accept the schedule dict and
responsible zone/sprinkler id and emit the "Ignoring stale EXECUTING schedule
id=%s zone=%s end_time=%s" debug line there, then remove the duplicate debug
calls from process_schedules and process_zone_schedules so formatting and
semantics remain consistent.
tests/conftest.py (1)

163-165: Fragile fixture dating — prefer monkeypatching time.time() over year 2099.

Moving id=100’s timestamps to 2099-04-07 is a pragmatic way to keep test_v2_schedule_iso_timestamp_parsing / test_v2_source_values exercising the “genuine EXECUTING” path without a monkeypatch, but it couples a broadly used shared fixture to the real wall clock. The tests that explicitly validate time-boundary behavior (e.g. test_schedule_actually_executing_time_boundary) already demonstrate the cleaner pattern — monkeypatch.setattr("device_handlers.time.time", ...) — which eliminates time coupling entirely.

Optional: restore realistic dates in the fixture and monkeypatch device_handlers.time.time inside the two tests that depend on the EXECUTING entry being “live”. Tests would then be insensitive to the system clock and future changes to tz handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/conftest.py` around lines 163 - 165, The shared fixture in
tests/conftest.py hardcodes entry id=100 timestamps to year 2099 which couples
tests to the real clock; revert those timestamps to realistic values and instead
update the two tests that rely on the entry being "EXECUTING"
(test_v2_schedule_iso_timestamp_parsing and test_v2_source_values) to
monkeypatch device_handlers.time.time to a fixed epoch that makes id=100 fall
within the executing window (use the pattern shown in
test_schedule_actually_executing_time_boundary via
monkeypatch.setattr("device_handlers.time.time", ...)); keep the fixture
reusable for other tests and only manipulate time inside the specific tests that
need the live EXECUTING state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Netro` Sprinklers.indigoPlugin/Contents/Info.plist:
- Line 6: The PluginVersion in Info.plist is set to an already-existing tag
(2026.4.5) causing CI version-check failures; update the <string> value for
PluginVersion in Netro Sprinklers.indigoPlugin/Contents/Info.plist to the next
patch release (e.g., 2026.4.6) so the release workflow can create the new
tag—locate the PluginVersion entry (the <string> value currently "2026.4.5") and
change it to the bumped version, then commit the change.

---

Nitpick comments:
In `@Netro` Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py:
- Around line 237-245: The V1 branch contains a dead ternary that returns
float(raw_value) in both cases; replace the conditional expression with a single
return float(raw_value) to simplify the timestamp parsing logic (leave the
surrounding try/except and the debug log referencing raw_value and api_version
unchanged). Locate the timestamp parsing code that checks api version V1 and
uses raw_value (the line currently: return float(raw_value) if
isinstance(raw_value, str) else float(raw_value)) and change it to a single
return of float(raw_value).
- Around line 178-181: Both SprinklerHandler.process_schedules and
ZoneHandler.process_zone_schedules duplicate the same debug message when a
schedule is considered stale; centralize that logging by moving the debug emit
into the single decision point _schedule_actually_executing (or create a small
helper called e.g. log_stale_executing_schedule called by both callers). Update
_schedule_actually_executing (or the new helper) to accept the schedule dict and
responsible zone/sprinkler id and emit the "Ignoring stale EXECUTING schedule
id=%s zone=%s end_time=%s" debug line there, then remove the duplicate debug
calls from process_schedules and process_zone_schedules so formatting and
semantics remain consistent.

In `@tests/conftest.py`:
- Around line 163-165: The shared fixture in tests/conftest.py hardcodes entry
id=100 timestamps to year 2099 which couples tests to the real clock; revert
those timestamps to realistic values and instead update the two tests that rely
on the entry being "EXECUTING" (test_v2_schedule_iso_timestamp_parsing and
test_v2_source_values) to monkeypatch device_handlers.time.time to a fixed epoch
that makes id=100 fall within the executing window (use the pattern shown in
test_schedule_actually_executing_time_boundary via
monkeypatch.setattr("device_handlers.time.time", ...)); keep the fixture
reusable for other tests and only manipulate time inside the specific tests that
need the live EXECUTING state.
🪄 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: 80300233-a9b1-401e-bec6-8a197211fa1c

📥 Commits

Reviewing files that changed from the base of the PR and between 5c4f050 and 5fd5c62.

📒 Files selected for processing (5)
  • Netro Sprinklers.indigoPlugin/Contents/Info.plist
  • Netro Sprinklers.indigoPlugin/Contents/Server Plugin/device_handlers.py
  • tests/conftest.py
  • tests/test_device_handlers.py
  • tests/test_zone_handler.py

Comment thread Netro Sprinklers.indigoPlugin/Contents/Info.plist Outdated
simons-plugins and others added 2 commits April 26, 2026 13:11
Netro sometimes leaves a completed schedule flagged "EXECUTING" long
after its end_time has passed — seen with MANUAL runs on Pixie
controllers. Trusting status alone leaves the controller's activeZone
and the zone's isIrrigating stuck on True for days.

Add SprinklerHandler._schedule_actually_executing() which requires both
status == "EXECUTING" AND (end_time missing OR end_time in the future).
Fall through to trusting status when end_time can't be verified, so a
genuinely-running manual schedule without end_time still registers as
active.

Use the guard in:
- SprinklerHandler.process_schedules (controller-level activeZone/
  activeSchedule)
- ZoneHandler.process_zone_schedules (per-zone isIrrigating). A stale
  EXECUTING on the zone's own history is demoted to lastWatering so the
  record still shows when the run actually happened.

Fixtures updated: sample_v2_schedules and sample_schedules_response had
past end_times on their EXECUTING entries; bumped to 2099/year-4096 so
they continue exercising the genuine-running path.

Bumps PluginVersion 2026.4.4 → 2026.4.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review-driven follow-up to the stale-EXECUTING fix:

Critical:
- Overwrite status to "EXECUTED" when demoting a stale EXECUTING to
  last_schedule, so the UI no longer reports
  "Last watering: Executing" for a watering that has actually ended.

Important:
- Harden _schedule_actually_executing against a non-dict entry in the
  schedules array; return False (AttributeError previously escaped the
  callers' except (KeyError, TypeError)).
- Log the stale-EXECUTING demotion at debug level in both handlers so
  the suppression is observable when diagnosing a "why is my zone
  suddenly idle?" report.
- Log unparseable timestamps at debug in _parse_schedule_sort_key —
  both the "trust status" fallback and the "never next" sort result are
  now traceable.
- Reorder inf check before V1 ms→seconds divide; the divide is still
  safe today (inf/1000 == inf) but the ordering is less fragile for
  future refactors.
- Replace brittle 3-day-past ISO dates in V2 tests with 2000-01-01 so
  assertions aren't clock-dependent.

Test additions:
- Multi-EXECUTING in same payload (controller picks genuine, zone
  isIrrigating stays True).
- id-comparison path when a stale EXECUTING coexists with a real
  EXECUTED (both orderings).
- Boundary test via monkeypatched time.time (past / exactly-now / future).
- V2 tz-aware ISO (+00:00) stale detection.
- Unparseable end_time string falls back to trusting status.
- Non-dict schedule entry returns False instead of raising.
- V2 stale test now asserts lastWateringSource and lastWateringStatus
  for symmetry with V1.

Drop redundant section-banner comment in test_device_handlers.py.

451 tests pass (was 443). Pylint 9.67/10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@simons-plugins simons-plugins force-pushed the fix/stuck-executing-schedule branch from 5fd5c62 to 74dadba Compare April 26, 2026 12:11
@simons-plugins simons-plugins merged commit c7048fd into main Apr 26, 2026
3 checks passed
@simons-plugins simons-plugins deleted the fix/stuck-executing-schedule branch April 26, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant