You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
An action that throws after Stop() is called is silently lost: RethrowExceptions() reports nothing #56
RethrowExceptions() (IntervalAction/IntervalAction.cs:260-266) inspects only PollingTask.Exception. An action's exception reaches PollingTask only when a later polling tick calls TryRun(), which sees the faulted ActionTask and throws (:207-215).
If Stop() is called while an action is still running, the polling loop exits normally and no later tick happens. When that in-flight action then throws, its exception stays on the faulted ActionTask, where nothing ever reads it. A later Restart() then discards it on purpose (:170-177, _ = ActionTask.Exception; ActionTask = null;).
Repro (verified against the built library)
varia=IntervalAction.Start(newIntervalActionOptions{PollingInterval=TimeSpan.FromMilliseconds(50),ActionInterval=TimeSpan.FromHours(1),Action=()=>{Thread.Sleep(300);thrownewInvalidOperationException("boom");},});awaitTask.Delay(100);ia.Stop();// action still runningawaitTask.Delay(1000);// action has thrown by nowia.RethrowExceptions();// Actual: returns normally. Expected: throws "boom".
Without Stop(), the same setup throws "boom" from RethrowExceptions() as documented.
Why it matters
The README and CLAUDE.md say that exceptions from actions are captured and can be rethrown with RethrowExceptions(). The natural shutdown sequence, Stop() then RethrowExceptions(), swallows a failure from the final run without any sign, which is exactly when a caller checks for errors.
Suggested fix
In RethrowExceptions(), when PollingTask has not faulted, also check ActionTask is { IsFaulted: true } and throw ActionTask.Exception.GetBaseException(). Alternatively, have the loop await and observe the in-flight ActionTask before it exits after a stop.
Decide explicitly whether Restart() should keep an unobserved action exception instead of discarding it.
Acceptance: a test that throws from an action still running when Stop() is called sees that exception from RethrowExceptions() once the action has finished.
Priority: Medium. The usual shutdown sequence, Stop() then RethrowExceptions(), loses the error from the final run without any sign. Nothing crashes and nothing is corrupted, but it breaks the documented exception contract.
Area / suggested assignee: exception propagation in IntervalAction.cs (RethrowExceptions, Restart). Owner: @matt-edmondson
Activity
matt-edmondson commentedon Sep 27, 2026
Triage
Stop()thenRethrowExceptions(), loses the error from the final run without any sign. Nothing crashes and nothing is corrupted, but it breaks the documented exception contract.IntervalAction.cs(RethrowExceptions,Restart). Owner: @matt-edmondsonRestartCoreAsyncso it waits for the stopped polling loop. That touches the same stop/restart path but does not observeActionTask, so it does not fix this issue. Coordinate with it, because the second bullet here (shouldRestart()discard an unobserved exception?) touches the same code.RethrowExceptions(), checkActionTask is { IsFaulted: true }as well, and add the acceptance test.Generated by Claude Code
Report an action that throws after Stop() from RethrowExceptions [patch]