ARCPClient._subscriptions is populated at src/arcp/_client/ops.py:164 for every successful client.subscribe(job_id) and removed only by client.unsubscribe(job_id) at src/arcp/_client/ops.py:180. When the upstream job terminates, _on_job_terminal in src/arcp/_client/dispatch.py:117 pops the entry from _handles and resolves the terminal future, but the matching _subscriptions[job_id] entry is never removed; _fail_all_handles at src/arcp/_client/client.py:177 clears _handles but not _subscriptions. The leftover JobSubscription keeps a reference to the now-terminal JobHandle, so a long-lived client that subscribes to many short jobs accumulates entries (and their associated handles, which hold bytes of past chunks and event histories pinned by closed queues) until client.close(). Users following the examples/subscribe/client.py pattern have no signal that they should call unsubscribe; the docstring at src/arcp/_client/handles.py:117 does not mention manual cleanup.
Fix prompt: When a job's terminal envelope arrives, treat the subscription as ended. In _on_job_terminal at src/arcp/_client/dispatch.py:114, pop the entry from client._subscriptions alongside client._handles. Update _fail_all_handles at src/arcp/_client/client.py:177 to also clear _subscriptions. Document the lifecycle in the JobSubscription and JobHandle docstrings: terminal events end the subscription automatically; clients only need to call unsubscribe() to opt out before completion. Add a regression test that subscribes to a job, runs it to completion, asserts client._subscriptions is empty, and asserts no JobSubscription is referenced from gc.get_referrers(handle) after the terminal event.
ARCPClient._subscriptionsis populated atsrc/arcp/_client/ops.py:164for every successfulclient.subscribe(job_id)and removed only byclient.unsubscribe(job_id)atsrc/arcp/_client/ops.py:180. When the upstream job terminates,_on_job_terminalinsrc/arcp/_client/dispatch.py:117pops the entry from_handlesand resolves the terminal future, but the matching_subscriptions[job_id]entry is never removed;_fail_all_handlesatsrc/arcp/_client/client.py:177clears_handlesbut not_subscriptions. The leftoverJobSubscriptionkeeps a reference to the now-terminalJobHandle, so a long-lived client that subscribes to many short jobs accumulates entries (and their associated handles, which holdbytesof past chunks and event histories pinned by closed queues) untilclient.close(). Users following theexamples/subscribe/client.pypattern have no signal that they should callunsubscribe; the docstring atsrc/arcp/_client/handles.py:117does not mention manual cleanup.Fix prompt: When a job's terminal envelope arrives, treat the subscription as ended. In
_on_job_terminalatsrc/arcp/_client/dispatch.py:114, pop the entry fromclient._subscriptionsalongsideclient._handles. Update_fail_all_handlesatsrc/arcp/_client/client.py:177to also clear_subscriptions. Document the lifecycle in theJobSubscriptionandJobHandledocstrings: terminal events end the subscription automatically; clients only need to callunsubscribe()to opt out before completion. Add a regression test that subscribes to a job, runs it to completion, assertsclient._subscriptionsis empty, and asserts noJobSubscriptionis referenced fromgc.get_referrers(handle)after the terminal event.