Skip to content

feat(reminders): expiration, self-cancellation, and delivery timeout fix#752

Merged
Aaronontheweb merged 4 commits intodevfrom
claude-wt-completed-reminders
Apr 26, 2026
Merged

feat(reminders): expiration, self-cancellation, and delivery timeout fix#752
Aaronontheweb merged 4 commits intodevfrom
claude-wt-completed-reminders

Conversation

@Aaronontheweb
Copy link
Copy Markdown
Collaborator

Summary

Two production reminder failures exposed lifecycle and timeout gaps:

  • Recurring reminders never expire: The akka-pr-8186-check1 interval reminder fired 60+ times over 21 hours after its purpose was fulfilled, because there was no expiration mechanism and the stuck session reported "success" in 5ms without the LLM ever running.
  • Delivery observation timeout too short: The dance-recital-115 OneShot reminder completed successfully (Slack message delivered, user replied) but was reported as failed because DeliveryObservedTimeout (30s) expired 3 seconds before the 33-second LLM turn finished.

Changes

Expiration — recurring reminders now support an optional ExpiresAt timestamp:

  • Added ExpiresAtMs/ExpiresAt to ReminderDefinition and ReminderInfo (backwards-compatible nullable field)
  • Expiration check in fire handler — disables before executing or rescheduling
  • Expired recurring reminders cleaned up during startup reconciliation
  • ExpiresIn parameter on set_reminder tool, REST API, and --expires-in CLI flag

Self-cancellation — recurring reminder prompts now include guidance telling the LLM to call cancel_reminder when the task's purpose is permanently fulfilled (e.g., PR merged, deploy completed), including the reminder's own ID.

Delivery timeoutDeliveryObservedTimeout aligned with ExecutionTimeoutSeconds (300s). A delivery-required reminder is now allowed the full execution window for the LLM to produce output.

Cleanup — replaced 4 hand-rolled FakeTimeProvider classes in Netclaw.Actors.Tests with the Microsoft.Extensions.Time.Testing.FakeTimeProvider that was already a package dependency. Renamed FormatNextFireFormatTimestamp since it now formats both next-fire and expiration timestamps.

Related

Test plan

  • dotnet build — zero errors, zero warnings
  • dotnet test — 1152 tests pass (Actors.Tests), 90 reminder-specific tests pass
  • dotnet slopwatch analyze — no new violations
  • Manual: create interval reminder with --expires-in 2m, verify auto-disable after expiration
  • Manual: verify cancel_reminder guidance appears in recurring reminder execution prompts
  • Manual: verify delivery observation succeeds for LLM turns >30s

…meout (#741)

Recurring reminders fired indefinitely when their purpose was fulfilled
(e.g., PR merged) because there was no expiration or self-completion
mechanism. Separately, Mode B delivery observation timed out at 30s
while LLM turns regularly take 30-60s, causing false failures.

Expiration:
- Add ExpiresAt (nullable) to ReminderDefinition and ReminderInfo
- Check expiration on fire before executing or rescheduling
- Disable expired recurring reminders during startup reconciliation
- Add ExpiresIn parameter to set_reminder tool, REST API, and CLI

Self-completion:
- Add complete_reminder tool (soft-disable, preserves history)
- Add prompt guidance for recurring reminders with reminder ID

Delivery timeout:
- Align DeliveryObservedTimeout with ExecutionTimeoutSeconds (300s)

Spec and tests:
- Add 3 requirement sections and 9 scenarios to scheduling spec
- Add CompleteReminderToolTests, expiration tests for manager and tool
… for self-completion

CompleteReminderTool added an unnecessary fifth scheduling tool that
could confuse smaller models. The LLM can call cancel_reminder when a
recurring reminder's purpose is fulfilled — no separate concept needed.

Also from code review:
- Remove redundant CancelScheduleOnlyAsync in reconciliation loop
  (DisableReminderInternalAsync already calls it)
- Rename FormatNextFire → FormatTimestamp (now used for both next-fire
  and expiration timestamps)
- Update stale DeliveryObservedTimeout comment to reflect new intent
…ensions.Time.Testing

Four test files in Netclaw.Actors.Tests defined identical private
FakeTimeProvider classes despite the project already referencing the
Microsoft.Extensions.TimeProvider.Testing package. Replace them with
the official FakeTimeProvider from Microsoft.Extensions.Time.Testing.
Prevent queued recurring reminders from executing after expiration and reject one-shot reminders that include expiration metadata. Also align reminder API/CLI/operations guidance so expiration is visible and documented consistently.
@Aaronontheweb Aaronontheweb marked this pull request as ready for review April 26, 2026 00:01
@Aaronontheweb Aaronontheweb enabled auto-merge (squash) April 26, 2026 00:01
@Aaronontheweb Aaronontheweb added the reminders Reminder scheduling, execution, and history label Apr 26, 2026
@Aaronontheweb Aaronontheweb disabled auto-merge April 26, 2026 00:02
@Aaronontheweb Aaronontheweb enabled auto-merge (squash) April 26, 2026 00:02
@Aaronontheweb Aaronontheweb merged commit f7827bc into dev Apr 26, 2026
4 of 5 checks passed
@Aaronontheweb Aaronontheweb deleted the claude-wt-completed-reminders branch April 26, 2026 00:04
Aaronontheweb added a commit that referenced this pull request Apr 29, 2026
)

* chore(openspec): archive 19 completed changes and sync delta specs

Archive all implemented OpenSpec changes, syncing their delta specs to
main specs in chronological order before moving to archive. Resolves
13 cross-change spec conflicts by applying older changes first so
newer implementations take precedence.

Archived changes:
- add-discord-init-reminder-dm-support
- background-job-execution
- channel-ingress-attachments
- channels-content-delivery-guarantees
- containerize-daemon-and-evals
- device-pairing
- discord-channel-with-interactions
- exposure-modes
- graceful-config-restart-drain
- hub-auth-framework
- inbound-webhooks
- mcp-audience-tool-grants
- mcp-server-approval-defaults
- multi-speaker-attribution
- public-audience-security-hardening
- session-cwd-tracking
- structured-tool-call-metadata
- tool-approval-gates
- working-context-grounding

New main specs created: audience-context-filtering, daemon-container,
daemon-exposure, device-pairing, feature-selection-wizard, hub-auth,
inbound-webhooks, netclaw-discord-socket, project-instructions,
session-cwd, tool-call-metadata

Remaining active changes: reminder-delivery-contract (partial),
subagent-explicit-model-selection (blocked on #648)

* chore(openspec): archive reminder-delivery-contract and sync specs

Implementation is complete (PR #692, #752). Remaining eval cases
tracked in #793.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reminders Reminder scheduling, execution, and history

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant