Drop deprecated `Context.result()` wrapper

Use `Context.wait_for_result()` directly at the nine remaining
call sites, including the debugger's root stdio-lock handshake.
Preserve `hide_tb=False` in the payload-spec test and keep the
outcome-renaming TODO beside the supported API.

Update the API removal note, ctx-specific comments and drain
diagnostics, and include the API-removal Prompt-IO record.

Prompt-IO: ai/prompt-io/opencode/20260922_context_result_removal.md

(this patch was generated in some part by `opencode`
using `gpt-6-astra` (`openai`))
ipc_fault_regressions
Gud Boi 2026-09-23 17:59:46 -04:00
parent bcb58f2af5
commit 299765508e
16 changed files with 216 additions and 47 deletions

View File

@ -0,0 +1,99 @@
---
model: openai/gpt-6-astra
service: opencode
session: unavailable
timestamp: 2026-09-22T15:43:25Z
git_ref: 19a61e6ea14ec70b0e0f9a24d207ea8d88bd4c4c
scope: code
substantive: true
raw_file: 20260922_context_result_removal.raw.md
---
## Prompt
The human approved descriptor removal and requested Context.result
removal as a distinct small followup. Work only in the Tractor
context_fault_status worktree. First save the exact simplified status
boundary, including intended tests and provenance, using a private
index. Exclude .gitignore and .ai. Preserve the user-owned real index
containing the original descriptor-based implementation. Save tree
OIDs and binary patches under ignored .claude runtime state.
Remove only the deprecated wrapper, migrate the nine identified calls,
preserve hide_tb=False, and update stale Context.result references and
API documentation. Preserve Portal/future result APIs, maintained
notes and TODOs, and the warnings import. Verify affected suites with
the supplied interpreter and PYTHONPATH, keeping reaping disabled and
reporting fixture blockers. Downstream changes belong to the parent.
## Response summary
Saved the pre-removal status tree before changing working source, then
removed Context.result and migrated all nine calls. wait_for_result
source is byte-for-byte unchanged. Updated documentation, diagnostic
strings and Context-specific comments, including the payload message
API reference. Moved the still-relevant outcome-renaming TODO beside
wait_for_result without changing its checkbox state.
Existing public-result tests cover falsey payloads and repeated waits.
No additional mechanical API-presence regression was needed.
## Files changed
- tractor/_context.py: wrapper removal and preserved TODO relocation.
- tractor/devx/debug/_tty_lock.py: debugger context-result await.
- tests/test_inter_peer_cancellation.py: one call and references.
- tests/msg/test_pldrx_limiting.py: one call, hide_tb=False retained.
- tests/test_shm.py: one call.
- tests/test_infected_asyncio.py: one call.
- tests/test_context_stream_semantics.py: four calls and references.
- docs/api/context.rst: removal note and replacement.
- tractor/_exceptions.py, tractor/_streaming.py: comments/docstrings.
- tractor/msg/_ops.py: drain diagnostic names the supported API.
- tractor/msg/types.py: payload API reference.
- tractor/to_asyncio.py: Context API comment.
- tests/test_oob_cancellation.py: Context-specific TODO reference.
- This new provenance pair records only the separate API removal.
## Verification
All commands ran from the context_fault_status worktree, using:
```xsh
$PYTHONPATH = '/home/goodboy/repos/tractor/wkts/context_fault_status:/home/goodboy/repos/tractor/wkts/ib_disconnect_cleanup/py313/lib/python3.13/site-packages:/home/goodboy/repos/piker/py313/lib/python3.13/site-packages'
py = '/home/goodboy/repos/tractor/wkts/ib_disconnect_cleanup/py313/bin/python'
@(py) -m pytest -p no:tractor._testing._reap tests/test_context_status.py tests/test_context_stream_semantics.py tests/test_ipc_eof.py -q
@(py) -m pytest -p no:tractor._testing._reap tests/msg/test_pldrx_limiting.py -q
@(py) -m pytest -p no:tractor._testing._reap tests/test_shm.py -q
```
Results: 90, 8 and 7 passed respectively; 105 passed total.
No failures or skips in those runs. Tractor import resolved inside
this worktree. TRACTOR_SPAWN_METHOD and TRACTOR_LOGLEVEL were unset;
ambient VIRTUAL_ENV pointed to piker/py313, but the explicit interpreter
above was used throughout.
Not run: inter-peer and infected-asyncio suites require
track_orphaned_uds_per_test from the disabled reaper plugin;
infected-asyncio also requests reap_subactors_per_test. This is an
explicit coverage limitation, not a passing or skipped test result.
Debugger integration was not separately executed.
A private AST audit confirms exactly nine migrated calls and no other
executable structural changes beyond deleting the wrapper (diagnostic
strings are normalized for that comparison). An independent source
comparison confirms wait_for_result itself is unchanged. Added Python
lines fit 69 columns; git diff --check passes. Final wrapping edits
after the test runs changed only formatting, confirmed by the audit.
## Human edits
The human explicitly selected API removal as a separate followup,
identified the nine call sites and downstream ownership, constrained
the implementation to wrapper removal, and required private snapshots,
real-index preservation, fixture-blocker reporting and provenance.
These are human-directed agent edits. No direct manual source edits
were observed during this followup. Acceptance remains human-owned.
The earlier provenance pair, including its original raw record, was
preserved unchanged; its result-method retention describes the prior
status boundary, superseded only by this separate followup.

View File

@ -0,0 +1,66 @@
---
model: openai/gpt-6-astra
service: opencode
timestamp: 2026-09-22T15:43:25Z
git_ref: 19a61e6ea14ec70b0e0f9a24d207ea8d88bd4c4c
diff_cmd: git diff HEAD~1..HEAD
---
## Generated response
Remove the deprecated Context.result wrapper as a separate followup
to the simplified ContextStatus migration. Migrate nine calls across
six files to wait_for_result, retaining hide_tb=False. Document the
removal and update Context-specific comments and diagnostic strings.
Keep the outcome-renaming TODO with wait_for_result.
> `git diff HEAD~1..HEAD -- tractor/_context.py`
Delete only the wrapper; wait_for_result source is byte-for-byte
unchanged. Keep the warnings import used by send_yield.
> `git diff HEAD~1..HEAD -- tractor/devx/debug/_tty_lock.py`
> `git diff HEAD~1..HEAD -- tests/test_inter_peer_cancellation.py`
> `git diff HEAD~1..HEAD -- tests/msg/test_pldrx_limiting.py`
> `git diff HEAD~1..HEAD -- tests/test_shm.py`
> `git diff HEAD~1..HEAD -- tests/test_infected_asyncio.py`
> `git diff HEAD~1..HEAD -- tests/test_context_stream_semantics.py`
Migrate the debugger lock call and eight test calls, including four
context-stream calls. Preserve arguments and surrounding behavior.
> `git diff HEAD~1..HEAD -- docs/api/context.rst`
> `git diff HEAD~1..HEAD -- tractor/_exceptions.py`
> `git diff HEAD~1..HEAD -- tractor/_streaming.py`
> `git diff HEAD~1..HEAD -- tractor/msg/_ops.py`
> `git diff HEAD~1..HEAD -- tractor/msg/types.py`
> `git diff HEAD~1..HEAD -- tractor/to_asyncio.py`
> `git diff HEAD~1..HEAD -- tests/test_oob_cancellation.py`
Replace stale Context.result references with the supported method.
The API page explicitly records removal and its replacement.
## Verification output
Context-status, context-stream and IPC EOF: 90 passed in 76.04s.
Payload limiting, separate process: 8 passed in 3.64s.
Shared memory, separate process: 7 passed in 5.17s.
Total: 105 passed, no failures or skips in these runs.
Inter-peer and infected-asyncio suites were not run: their module
fixture requirements depend on the disabled reaper plugin. No cleanup
plugin was enabled. Debugger integration was not separately executed.
9 call migrations; wait_for_result source unchanged; AST OK
All added Python lines fit 69 columns
git diff --check passed. Existing public-result tests already cover
falsey outcomes and repeated waits; no new presence-only test was added.
Original staged index SHA-256 remained unchanged through verification.
## Diff boundary
The eventual HEAD~1..HEAD pointers assume this pair accompanies only
the API-removal followup. Before committing, use the private snapshot
patches under .claude/wkts/context-status-boundary instead. The
pre-removal tree is 94858f83875d9809b67d517f2b0bc52ccbd09ded.

View File

@ -51,9 +51,10 @@ The ``@context`` decorator
maybe_error,
outcome
.. deprecated:: 0.1.0a6
.. note::
``Context.result()`` warns; use :meth:`Context.wait_for_result`.
``Context.result()`` has been removed; replace calls with
:meth:`Context.wait_for_result`.
.. note::

View File

@ -359,7 +359,9 @@ def test_basic_payload_spec(
assert first.field == 'yo'
try:
res: None|PldMsg = await ctx.result(hide_tb=False)
res: None|PldMsg = await ctx.wait_for_result(
hide_tb=False,
)
assert res is None
except MsgTypeError as mte:
maybe_mte = mte

View File

@ -55,7 +55,7 @@ from ._helpers import (
# call ``Context.started()`` to unblock this entry on the parent side.
# the child task executes until complete and returns a final value
# which is delivered to the parent side and retreived via
# ``Context.result()``.
# ``Context.wait_for_result()``.
# - cancel termination:
# context can be cancelled on either side where either end's task can
@ -514,7 +514,9 @@ def test_simple_context(
if child_blocks_forever:
await portal.run(assert_state, value=True)
else:
assert await ctx.result() == 'yo'
assert (
await ctx.wait_for_result() == 'yo'
)
if pointlessly_open_stream:
async with ctx.open_stream():
@ -593,7 +595,7 @@ def test_parent_cancels(
'''
Verify that when the opening side of a context (aka the parent)
cancels that context, the ctx does not raise a cancelled when
either calling `.result()` or on context exit.
either calling `.wait_for_result()` or on context exit.
'''
async def check_canceller(
@ -608,7 +610,7 @@ def test_parent_cancels(
and not child_returns_early
):
try:
res = await ctx.result()
res = await ctx.wait_for_result()
assert 0, 'Portal cancel should raise!'
except ContextCancelled as ctxc:
@ -624,7 +626,7 @@ def test_parent_cancels(
# silently!
else:
try:
res = await ctx.result()
res = await ctx.wait_for_result()
except ContextCancelled as ctxc:
pytest.fail(f'should not have raised ctxc\n{ctxc}')
@ -726,8 +728,9 @@ def test_parent_cancels(
#
# WHY: the `.cancel_actor()` case (cancel_method='portal')
# will cause both:
# * the `ctx.result()` inside `.open_context().__aexit__()`
# * AND the `ctx.result()` inside `check_canceller()`
# * `ctx.wait_for_result()` inside
# `.open_context().__aexit__()`
# * AND `ctx.wait_for_result()` in `check_canceller()`
# to raise ctxc.
#
# which should in turn cause `ctx._scope` to
@ -1504,7 +1507,9 @@ def test_maybe_allow_overruns_stream(
print('Requesting `ctx.cancel()` in parent!')
await ctx.cancel()
res: str|ContextCancelled = await ctx.result()
res: str|ContextCancelled = (
await ctx.wait_for_result()
)
if cancel_ctx:
assert isinstance(res, ContextCancelled)

View File

@ -400,7 +400,7 @@ def test_context_spawns_aio_task_that_errors(
async with expect_ctxc(
yay=parent_cancels == 'actor',
):
await ctx.result()
await ctx.wait_for_result()
if parent_cancels == 'context':
# to tear down sub-acor

View File

@ -468,12 +468,13 @@ def test_peer_canceller(
try:
print('PRE CONTEXT RESULT')
res = await sleeper_ctx.result()
res = await sleeper_ctx.wait_for_result()
assert res
# should never get here
pytest.fail(
'Context.result() did not raise ctx-cancelled?'
'Context.wait_for_result() did not '
'raise ctx-cancelled?'
)
# should always raise since this root task does
@ -650,7 +651,8 @@ def test_peer_canceller(
# propagated
#
else:
# since sleeper_ctx.result() IS called above
# since sleeper_ctx.wait_for_result() IS called
# above
# we should have (silently) absorbed the
# corresponding `ContextCancelled` for it and
# `._scope.cancel()` should never have been
@ -694,7 +696,7 @@ def test_peer_canceller(
# the recorded local erro should always be
# the same as the one raised by the
# `sleeper_ctx.result()` call
# `sleeper_ctx.wait_for_result()` call
assert (
le
and
@ -715,7 +717,8 @@ def test_peer_canceller(
assert not ctx.cancel_called
assert not ctx.cancel_acked
# since sleeper_ctx.result() IS called
# since sleeper_ctx.wait_for_result()
# IS called
# above we should have (silently)
# absorbed the corresponding
# `ContextCancelled` for it and thus
@ -736,7 +739,8 @@ def test_peer_canceller(
# a `Portal.cancel_actor()` to the
# sleeper and thus never should call
# `ctx.cancel()` per say UNLESS the
# sleeper's `.result()` call above
# sleeper's `.wait_for_result()` call
# above
# ctxc exception results in the
# canceller's
# `.open_context().__aexit__()` error
@ -1176,7 +1180,8 @@ def test_peer_spawns_and_cancels_service_subactor(
await client_ctx.cancel()
print(
'-> root cancelling client,\n'
'-> root checking `client_ctx.result()`,\n'
'-> root checking '
'`client_ctx.wait_for_result()`,\n'
f'-> checking that sub-spawn {peer_name} is down\n'
)

View File

@ -40,7 +40,7 @@ from tractor import ( # typing
# `raise scope_error from ctxc`?
#
# -[ ] a self-cancelled context, if not allowed to block on
# `ctx.result()` at some point will hang since the `ctx._scope`
# `ctx.wait_for_result()` will hang since the `ctx._scope`
# is never `.cancel_called`; cases for this include,
# - an `open_ctx()` which never starteds before being OoB actor
# cancelled.

View File

@ -80,7 +80,7 @@ def test_child_attaches_alot():
==
_key
)
await ctx.result()
await ctx.wait_for_result()
await portal.cancel_actor()

View File

@ -1473,6 +1473,10 @@ class Context:
raise remote_error
# TODO: consider renaming this API!
# -[ ] should be named `.wait_for_outcome()` and instead do
# a `.outcome.Outcome.unwrap()` ?
#
async def wait_for_result(
self,
hide_tb: bool = True,
@ -1579,24 +1583,6 @@ class Context:
# `self.outcome.unwrap()` here?
return self.outcome
# TODO: switch this with above!
# -[ ] should be named `.wait_for_outcome()` and instead do
# a `.outcome.Outcome.unwrap()` ?
#
async def result(
self,
*args,
**kwargs,
) -> Any|Exception:
log.warning(
'`Context.result()` is DEPRECATED!\n'
'Use `Context.[no]wait_for_result()` instead!\n'
)
return await self.wait_for_result(
*args,
**kwargs,
)
@property
def maybe_error(self) -> BaseException|None:
'''

View File

@ -1376,7 +1376,7 @@ def _raise_from_unexpected_msg(
and raised locally as -> `trio.EndOfChannel`
- All other mis-keyed msgss (like say a "final result"
'return' msg, normally delivered from `Context.result()`)
'return' msg, normally from `Context.wait_for_result()`)
are re-boxed inside a `MessagingError` with an explicit
exc content describing the missing IPC-msg-key.
@ -1433,7 +1433,8 @@ def _raise_from_unexpected_msg(
log.debug(message)
# TODO: if the a local task is already blocking on
# a `Context.result()` and thus a `.receive()` on the
# a `Context.wait_for_result()` and thus `.receive()`
# on the
# rx-chan, we close the chan and set state ensuring that
# an eoc is raised!
@ -1450,7 +1451,8 @@ def _raise_from_unexpected_msg(
# call (likely by another task using a broadcast receiver)
# doesn't accidentally pull the `return` message
# value out of the underlying feed mem chan which is
# destined for the `Context.result()` call during ctx-exit!
# destined for `Context.wait_for_result()` during
# ctx-exit!
stream._eoc: Exception = eoc
# in case there already is some underlying remote error

View File

@ -516,7 +516,7 @@ class MsgStream(trio.abc.Channel):
# is arguably more confusing since we lose detection of the
# runtime's closure of ``rx_chan`` in the case where we may
# still need to consume msgs that are "in transit" from the far
# end (eg. for ``Context.result()``).
# end (eg. for ``Context.wait_for_result()``).
# self._closed = True
return drained

View File

@ -1102,7 +1102,9 @@ async def request_root_stdio_lock(
# sync with child-side root locker task
# completion
status: LockStatus = await req_ctx.result()
status: LockStatus = (
await req_ctx.wait_for_result()
)
assert not status.locked
DebugStatus.lock_status = status

View File

@ -725,7 +725,8 @@ async def drain_to_final_msg(
# a final result or error/ctxc.
else:
report: str = (
'Ignoring "yield" msg during `ctx.result()` drain..\n'
'Ignoring "yield" msg during '
'`ctx.wait_for_result()` drain..\n'
f'<= {ctx.chan.aid.uid}\n'
f' |_{ctx._nsf}()\n\n'
f'=> {ctx._task}\n'

View File

@ -82,7 +82,7 @@ class PayloadMsg(
(i.e. any values passed by `tractor` application code using any of
|_ `._streaming.MsgStream.send/receive()`
|_ `._context.Context.started/result()`
|_ `._context.Context.started/wait_for_result()`
|_ `._ipc.Channel.send/recv()`
aka our "IPC primitive APIs")

View File

@ -679,7 +679,7 @@ def _run_asyncio_task(
# and then set any final `res` from above as a field
# that can optionally be read by the trio-paren-task as
# needed (just like in our
# `Context.wait_for_result()/.result` API yah?
# `Context.wait_for_result()` API yah?
#
# if provide_channels: