What happens
You delete an agent that has a schedule. The schedule stays enabled and keeps firing on its cadence. Every occurrence fails with "Agent ... does not exist", writes an error run to the audit trail, and may alert the owner.
With retry off, the default path, this repeats forever: applyFailureOutcome() only sets lastStatus: error and never pauses anything (ScheduleService.php:1638-1643). With retry on, it runs the retries, dead-letters, and stops only when the circuit breaker trips.
A schedule that requires approval is worse: each occurrence creates a fresh pending approval for an agent that no longer exists.
Why
findDueSchedules() selects every enabled schedule and checks only enabled and the due time (ScheduleService.php:840-867).
dispatch() runs the kill switch, budget and approval gates, then runDue(). None of them checks that agentId still resolves (ScheduleService.php:937-1011).
- The agent is first loaded inside the run itself, which throws (ScheduleService.php:2652-2659 on the engine path,
agentMapper->findByUuid() at :2308 on the legacy path).
- Nothing cascades an agent delete to its schedules.
AgentBotLifecycleListener handles delete and trash, but only uninstalls the Talk bot (AgentBotLifecycleListener.php:151-167).
Fix direction
Two parts, both small:
- On agent delete or trash, disable (or delete) the schedules whose
agentId is that agent. The bot listener already sees both events and treats trash as delete.
- As a backstop, have
dispatch() resolve the agent before the gates and record a skipped_agent_missing status that disables the schedule, instead of failing every tick.
Live check
- Create an agent and attach an interval schedule of a few minutes, retry off.
- Delete the agent from the agent catalog.
- Wait two occurrences. Open the schedule object or the run history:
lastStatus is error with "does not exist", and enabled is still true.
- Repeat with
requiresApproval on and check the approval inbox for new pending approvals.
Matrix rows: sc-cron and the scheduling area (openspec/parity/capabilities.json). The lane's FINAL report listed this as a suspected live defect.
What happens
You delete an agent that has a schedule. The schedule stays enabled and keeps firing on its cadence. Every occurrence fails with "Agent ... does not exist", writes an error run to the audit trail, and may alert the owner.
With retry off, the default path, this repeats forever:
applyFailureOutcome()only setslastStatus: errorand never pauses anything (ScheduleService.php:1638-1643). With retry on, it runs the retries, dead-letters, and stops only when the circuit breaker trips.A schedule that requires approval is worse: each occurrence creates a fresh pending approval for an agent that no longer exists.
Why
findDueSchedules()selects every enabled schedule and checks onlyenabledand the due time (ScheduleService.php:840-867).dispatch()runs the kill switch, budget and approval gates, thenrunDue(). None of them checks thatagentIdstill resolves (ScheduleService.php:937-1011).agentMapper->findByUuid()at :2308 on the legacy path).AgentBotLifecycleListenerhandles delete and trash, but only uninstalls the Talk bot (AgentBotLifecycleListener.php:151-167).Fix direction
Two parts, both small:
agentIdis that agent. The bot listener already sees both events and treats trash as delete.dispatch()resolve the agent before the gates and record askipped_agent_missingstatus that disables the schedule, instead of failing every tick.Live check
lastStatusiserrorwith "does not exist", andenabledis still true.requiresApprovalon and check the approval inbox for new pending approvals.Matrix rows:
sc-cronand the scheduling area (openspec/parity/capabilities.json). The lane's FINAL report listed this as a suspected live defect.