Skip to content

feat(runtime): end a stopped run as stopped, and stop a run on a debug quit - #171

Open
robert-ursu wants to merge 3 commits into
mainfrom
feat/stopped-status
Open

robert-ursu wants to merge 3 commits into
mainfrom
feat/stopped-status

Conversation

@robert-ursu

Copy link
Copy Markdown

A run that its host stops now ends as stopped instead of faulted, and a debug session that quits while the agent is running stops the run instead of letting it finish.

Why

  • Stopped looked like a crash. A host stops a run by cancelling it: uipath server does this for an Orchestrator stop or kill. The context turned the resulting CancelledError into a faulted result with ERROR_CancelledError. output.json and the result sink could not tell a stop from a failure.
  • A debug quit was ignored while the agent ran. The debug runtime looked at the bridge's terminate signal only while it was waiting: before the first resume, at a breakpoint, or while polling a trigger. A Quit from Studio Web during a run was logged and then ignored, and the run went on to completion.

What: a stopped status

  • New UiPathRuntimeStatus.STOPPED ("stopped").
  • New uipath.runtime.stop: a per-execution slot for a stop-requested probe that the host installs. It is a contextvar, like the output sinks, so concurrent jobs in one process each see their own host's answer.
  • UiPathRuntimeContext.__exit__ writes stopped, with no error, when two things hold:
    • the run ended in a cancellation, or in an error raised while handling one or because of one;
    • the probe says the host asked to stop.
  • The result sink receives a stopped result as it does a successful or faulted one.
  • The probe is read when the context starts, like the sinks.
  • Without a probe, or when it says no, nothing changes. A probe that raises is logged, and the run is reported as faulted.

What: a debug quit while the run is going

The delegate's stream is now watched for a quit:

  • While the delegate is working: the quit cancels it where it waits, as a stop would.
  • While an event is being handled: the stream ends before the next event.

Either way the delegate is closed, and the run ends with the result a quit has always produced. A cancellation from anywhere else still propagates.

Worth a careful look

  • The watch only cancels inside the delegate. _until_quit cancels the task only while it is inside the delegate's __anext__. It clears its own cancellation with Task.uncancel(), so a cancellation it did not send keeps propagating. If the delegate swallows the cancellation, the watch still takes it back.
  • wait_for_terminate() returning now ends a running session. The bridges in uipath (console and SignalR) and the detached bridge here block until the user quits. A bridge whose wait_for_terminate returns at once would end every run at its first event. One test fake in uipath-claude-sdk does this.
  • A quit still reports successful, not stopped. Handlers that predate the stopped status cannot parse it in output.json. A coded agent can also run a newer runtime than its host, so switching the quit's status is left for when those handlers are gone.

Compatibility

Testing

  • New tests/test_stop.py:
    • the probe is unset by default;
    • a cancellation the host asked for ends as stopped, with no error;
    • with no probe, or a probe that says no, a cancellation still faults with ERROR_CancelledError;
    • an error raised while stopping ends as stopped, and an unrelated error after a stop request still faults;
    • a run that completes after a stop request keeps its result;
    • a probe that raises reports the run as faulted;
    • the result sink receives the stopped result;
    • the probe is read when the context starts.
  • New in tests/test_debugger.py:
    • a quit while the delegate works cancels it and closes it;
    • a quit between events ends the stream before the next one;
    • a cancellation from elsewhere still propagates;
    • a quit at a breakpoint yields a single result.
  • The debug bridge mock's wait_for_terminate now blocks like a real bridge.
  • End to end, with feat(cli): report the real exit code and really stop what uipath server runs uipath-python#1842 against a real uipath server, a stopped job's output.json is {"output": {}, "status": "stopped"}.
  • 460 tests pass; ruff and mypy are clean.

🤖 Generated with Claude Code

robert-ursu and others added 2 commits October 5, 2026 14:54
A run cancelled because its host asked it to stop was reported as faulted
with ERROR_CancelledError, indistinguishable from a crash. A host can now
install a stop-requested probe (uipath.runtime.stop). When the run ends in
a cancellation, or in an error raised because of one, and the probe says a
stop was requested, the context writes the new stopped status with no
error, and the result sink receives it like any final result. Without a
probe, or when the probe says no, nothing changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The debug runtime looked at the bridge's terminate signal only while it
was waiting: before the first resume, at a breakpoint, or while polling a
trigger. A Quit from Studio Web while the agent was running was logged and
then ignored, and the run went on to completion.

The delegate's stream is now watched for a quit. One that arrives while
the delegate is working cancels it where it waits, as a stop would; one
that arrives while an event is being handled ends the stream before the
next. The delegate is closed either way and the run ends with the result a
quit has always produced. A cancellation from anywhere else still
propagates.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Debug quit can restart a cancellation-swallowing delegate and remains ignored by the non-streaming fallback.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds stopped-run reporting and makes debug quit interrupt active streaming executions.

Changes:

  • Introduces the STOPPED status and host stop-request probe.
  • Detects cancellation-related stops and delivers stopped results.
  • Watches streaming delegates for debug termination.
File Description
src/​uipath/​runtime/​stop.py Adds the stop-request probe.
src/​uipath/​runtime/​result.py Adds STOPPED status.
src/​uipath/​runtime/​context.py Classifies requested cancellations as stopped.
src/​uipath/​runtime/​debug/​runtime.py Adds debug-quit monitoring.
tests/​test_stop.py Tests stopped-result behavior.
tests/​test_debugger.py Tests debug termination handling.
pyproject.toml Bumps package version.
uv.lock Updates the locked project version.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/uipath/runtime/debug/runtime.py Outdated
Comment on lines 177 to 179
async for event in self._until_quit(
self.delegate.stream(current_input, options=debug_options)
):
…in execute()

A stopped run is now told apart from a failure by Task.cancelling() on the
task that entered the context: a cancel() someone called keeps it above
zero through cleanup, and a stray CancelledError leaves it at zero. This
replaces the host-installed stop-requested probe and the walk of the
exception chain, so uipath.runtime.stop goes away and hosts install
nothing.

The debug quit is watched in execute(), which runs the stream in its own
task and races it against wait_for_terminate(). A quit cancels that task
instead of the caller's, so no uncancel() bookkeeping or in-delegate
tracking is needed, and the execute() fallback for a delegate that cannot
stream is covered too.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_011be41L1VE33oixHNnJRPhw
@sonarqubecloud

sonarqubecloud Bot commented Oct 6, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants