The lease expiry watchdog is started for jobs with LeaseConstraints.ExpiresAt, but it is not cancelled when the agent completes before the lease expires. RunAsync emits the terminal result, marks the job terminal, enters finally, and then awaits the watchdog task. Because the job cancellation token was not cancelled by a successful completion, the watchdog can sleep until expires_at, emit a late tool_result lease-expired event after the terminal result, and keep the background job task alive for the full remaining lease duration.
The relevant locations are src/Arcp.Runtime/JobManager.cs:166, src/Arcp.Runtime/JobManager.cs:171, src/Arcp.Runtime/JobManager.cs:216, and src/Arcp.Runtime/JobManager.cs:274.
Fix prompt: Change JobManager.RunAsync so the lease watchdog is cancelled as soon as the job reaches any terminal state. Use a watchdog-specific CancellationTokenSource linked to the run token and cancel it in finally before awaiting the watchdog, or have the watchdog also stop when job.Status becomes terminal. Preserve the behavior where an actual lease expiry cancels the job and emits LEASE_EXPIRED. Add a test with a future ExpiresAt and an agent that returns immediately, then assert RunAsync completes promptly and no lease-expired event is emitted after job.result.
The lease expiry watchdog is started for jobs with
LeaseConstraints.ExpiresAt, but it is not cancelled when the agent completes before the lease expires.RunAsyncemits the terminal result, marks the job terminal, entersfinally, and then awaits the watchdog task. Because the job cancellation token was not cancelled by a successful completion, the watchdog can sleep untilexpires_at, emit a latetool_resultlease-expired event after the terminal result, and keep the background job task alive for the full remaining lease duration.The relevant locations are
src/Arcp.Runtime/JobManager.cs:166,src/Arcp.Runtime/JobManager.cs:171,src/Arcp.Runtime/JobManager.cs:216, andsrc/Arcp.Runtime/JobManager.cs:274.Fix prompt: Change
JobManager.RunAsyncso the lease watchdog is cancelled as soon as the job reaches any terminal state. Use a watchdog-specificCancellationTokenSourcelinked to the run token and cancel it infinallybefore awaiting the watchdog, or have the watchdog also stop whenjob.Statusbecomes terminal. Preserve the behavior where an actual lease expiry cancels the job and emitsLEASE_EXPIRED. Add a test with a futureExpiresAtand an agent that returns immediately, then assertRunAsynccompletes promptly and no lease-expired event is emitted afterjob.result.