src/arcp/_runtime/_handlers.py:74 schedules the event-log release with asyncio.create_task(runtime.event_log.release_through(ctx.session_id, ack.last_processed_seq)) and immediately drops the returned task reference. Three problems follow. First, the task object is never stored, so Python's garbage collector can delete it before it runs and only a RuntimeWarning will hint at the loss; the project's filterwarnings = ["error", "ignore::DeprecationWarning"] suppresses RuntimeWarning only incidentally and offers no observability. Second, if release_through raises (which is plausible for SqliteEventLog against a locked or full disk), the exception is captured on the orphan task and surfaces only when the loop tears down with "Task exception was never retrieved." Third, because the release runs concurrently with subsequent handle_ack calls, two acks landing back-to-back can race their DELETE statements without a serialization point. The runtime then quietly fails to release acked prefixes and the event log grows unbounded.
Fix prompt: Either make handle_ack async and await release_through directly, or store the spawned task on the SessionContext (e.g. a set[asyncio.Task[None]]) and attach an add_done_callback that logs any unhandled exception via runtime.logger.exception. The first option is cleanest; it also serializes release order per session and matches the behavior the spec expects. Add a test that uses a stub EventLog whose release_through raises, sends a session.ack, and asserts that the error is logged exactly once and that the session survives. Run uv run pyright to verify the new signature change does not break the dispatch table at src/arcp/_runtime/server.py:232.
src/arcp/_runtime/_handlers.py:74schedules the event-log release withasyncio.create_task(runtime.event_log.release_through(ctx.session_id, ack.last_processed_seq))and immediately drops the returned task reference. Three problems follow. First, the task object is never stored, so Python's garbage collector can delete it before it runs and only aRuntimeWarningwill hint at the loss; the project'sfilterwarnings = ["error", "ignore::DeprecationWarning"]suppressesRuntimeWarningonly incidentally and offers no observability. Second, ifrelease_throughraises (which is plausible forSqliteEventLogagainst a locked or full disk), the exception is captured on the orphan task and surfaces only when the loop tears down with "Task exception was never retrieved." Third, because the release runs concurrently with subsequenthandle_ackcalls, two acks landing back-to-back can race theirDELETEstatements without a serialization point. The runtime then quietly fails to release acked prefixes and the event log grows unbounded.Fix prompt: Either make
handle_ackasyncandawaitrelease_throughdirectly, or store the spawned task on theSessionContext(e.g. aset[asyncio.Task[None]]) and attach anadd_done_callbackthat logs any unhandled exception viaruntime.logger.exception. The first option is cleanest; it also serializes release order per session and matches the behavior the spec expects. Add a test that uses a stubEventLogwhoserelease_throughraises, sends asession.ack, and asserts that the error is logged exactly once and that the session survives. Runuv run pyrightto verify the new signature change does not break the dispatch table atsrc/arcp/_runtime/server.py:232.