Repository navigation
Conversation
…o start When the server process did not start listening within the timeout, start() read the whole stderr pipe before stopping the process. If the server was still running, for example because it listened on another URL, the read never returned and start() hung forever instead of raising AppiumServiceError. This affected the default configuration on non-Windows platforms, where stderr is piped. Terminate the process before collecting its stderr with communicate(), so the error is raised after the timeout and still includes the server output. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01K6zVKi1DtwmmBpy16MQyfY
KazuCocoa
reviewed
Oct 31, 2026
| try: | ||
| _, err_output = self._process.communicate(timeout=STOP_TIMEOUT_SEC) | ||
| except sp.TimeoutExpired: | ||
| err_output = None |
Member
There was a problem hiding this comment.
Is this intentional to lose error message?
Contributor
Author
There was a problem hiding this comment.
Good catch, that wasn't intended. If the server didn't exit within 5.5 s after terminate(), its stderr was dropped. cab5a2d now kills it in that case and reads stderr again, still with a timeout so start() can't hang. If something else keeps the pipe open, it keeps whatever was read so far. I added unit tests for both cases.
If the Appium server did not exit within STOP_TIMEOUT_SEC after terminate(), start() dropped its stderr, so the "Original error" part of the AppiumServiceError message was lost. Kill the process in that case and read stderr again, with the same timeout, so start() still cannot hang. If another process still holds stderr open, keep the output read so far. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
AppiumService.start()is supposed to raiseAppiumServiceErrorwhen the server does not start listening withintimeout_ms. On macOS and Linux, wherestderrdefaults tosubprocess.PIPE, it can hang forever instead.When startup fails, the error path calls
self._process.stderr.read()before stopping the process. If the server process is still running (for example, it started but listens on a different port, address or base path than the one parsed from the arguments, or it is slower than the timeout),read()waits for EOF on the pipe. That EOF only arrives when the process exits, so the call never returns andself.stop()is never reached.Reproduction: start a fake server that writes to stderr and sleeps, with
timeout_ms=1500.start()was still blocked after 20 seconds. Withstderr=subprocess.DEVNULLthe same call raised the expected error after 1.5 seconds.Fix
In the error path, terminate the process first, then collect its stderr with
communicate(timeout=...). The error message still includes the server output, as before. If the process ignoresSIGTERM, the existingstop()still force-kills it.STOP_TIMEOUT_SECis added sostart()andstop()share the same 5.5 second default.Tests
stderr.read()is not called, and that the stderr output is still included in the error message. Both cases fail without the fix.SIGTERMis killed, and the error is raised after about 12.5 seconds.ruff check,ruff format --check,mypy appiumand the full unit test suite (211 tests) pass.🤖 Generated with Claude Code