skip to content

Your Appium bakery pre-order lane disables appium:newCommandTimeout to survive long pauses — what does that cost?

level: seniorimportance: should knowfreq 40%

answer

  1. silence on the wire, not test activity
  2. zero removes a backstop, not a limit
  3. the next run inherits the device
  4. explicit delete becomes the only exit

basics

~20 s

Setting appium:newCommandTimeout to 0 turns off the server's idle reaper, so only an explicit session delete releases the device. A crashed or killed client then leaves the UiAutomator2 helper server running on Android, or WebDriverAgent running on iOS.

solid answer

~50 s

`appium:newCommandTimeout` is the server's dead-man switch: every answered command restarts it, and when it expires the server tears the session down exactly as `DELETE /session/:sessionId` would. Setting it to `0` disables that. It does buy a session that survives an arbitrarily long driver-silent pause — but it also means nothing except your own teardown ever releases the device. If the test process is killed, hits a CI step timeout, or crashes, the session stays open: on Android the UiAutomator2 helper server and its port forwarding stay up, and on iOS WebDriverAgent keeps running on the phone or simulator. The next run inherits that. The usual answer is not `0` but a value above your longest legitimate pause, plus teardown you can trust — so the reaper stays a backstop rather than becoming your only cleanup.

go deeper

for a junior

Know that appium:newCommandTimeout is counted in seconds and that setting it to 0 switches the server's idle cleanup off entirely. That is the fact the rest of this question is built on.

for a middle

Explain that the timer restarts on every answered command and measures silence the server sees rather than work your test is doing, and that expiry runs the same teardown as an explicit session delete.

for a senior

Show what is left behind when the reaper is off and a client dies: the UiAutomator2 helper server on Android or WebDriverAgent on iOS, and a device the next run wrongly assumes is clean. Say how you would detect it.

for a principal

Own the policy: whether the idle timer is a backstop or the primary cleanup, where an interactive profile may legitimately differ from a pipeline one, and what the lane does at start to guarantee a clean device regardless.

## What the idle reaper actually measures `appium:newCommandTimeout` is a capability counted in **seconds** that arms a timer on the Appium server. The timer restarts every time the server finishes answering a command for that session. If it expires before the next command arrives, the server deletes the session down the same teardown path that `DELETE /session/:sessionId` takes. Read that carefully, because the common mental model is wrong in one specific way: the timer measures **silence on the wire**, not activity in your test. The server has no idea whether your process is alive, whether an assertion loop is spinning, whether a debugger is stopped on a breakpoint, or whether the device is busy. It knows only how long it has been since it last answered a request for this session. A bakery pre-order test that polls its own backend for an order-confirmation webhook, without touching the driver, looks exactly like a dead client from the server's side. ## What zero buys, and what it removes Setting the capability to `0` disables the timer. From then on: - The session survives any pause of any length, which is the reason people reach for it. - The session is released only by an explicit `DELETE /session/:sessionId`, by the server process stopping, or by a driver-level failure that ends it. - **The server stops being your backstop.** Every path that used to end in "the reaper cleaned it up eventually" now ends in "the session is still open." That last point is the cost, and it is not a small one, because the things that skip your teardown are exactly the things the test cannot catch: a CI job hitting its own step timeout and killing the process group, a runner being reclaimed, an out-of-memory kill, a crash inside the client library, or a developer pressing Ctrl-C. ## What is left running, per platform | | Android (UiAutomator2 driver) | iOS (XCUITest driver) | |---|---|---| | what stays alive on the device | `io.appium.uiautomator2.server` and the `io.appium.settings` helper | the running WebDriverAgent session | | what stays alive on the host | the port forwarding to that helper server | the connection to the agent, plus its build output | | how the next session notices | a new session meets a helper server that is still holding the device | a new session collides with, or silently attaches to, an agent that is still running | The shape is the same on both — a device that is not as clean as the next run assumes — but the thing you have to clear away has a different name, lives in a different place, and is inspected with different tools. That is why "just restart the agent" is not one instruction across a mixed fleet. ## How the leak shows itself - A run that passed yesterday fails at session start today on the same device, and passes again once that device is rebooted. - Sessions get progressively slower across a long day on a self-hosted runner as leftovers accumulate. - The server's session list grows across runs even though the suite reports that every test finished. - The first test of a run fails and the rest pass — the classic signature of inherited device state rather than a bug in that first test. ## Safer shapes than zero 1. **Size the value, do not remove it.** Pick a number comfortably above the longest legitimate driver-silent pause your suite has, and treat a session that still gets reaped as evidence that the pause is longer than you believed. 2. **Do not go silent in the first place.** If the pause is your own polling loop, poll something through the driver, or break the wait up so the driver is touched periodically. A session that is never idle never meets the reaper. 3. **Make teardown unconditional.** Put the client's `quit` — the `DELETE /session/:sessionId` call — in the language's guaranteed-cleanup construct rather than at the end of the happy path, so the reaper is genuinely a backstop. 4. **Sweep at lane start.** If leftovers are possible at all, the run should begin by clearing them rather than trusting that the previous run was tidy. ## When zero is defensible There is one honest case: interactive work. When a person is driving a session by hand — stepping through a flow, reading the bakery pre-order app's page source, keeping the session alive while they think — the reaper is pure nuisance, and there is a human present to close the session afterwards. Even then the value belongs in an interactive-only profile rather than in the configuration a pipeline loads, because the failure mode of `0` in CI is precisely that nobody is present when the process dies.

  • If a bakery pre-order test must wait several minutes on a backend, what is better than disabling the idle timer?
    Keep the driver in the loop. Poll a cheap driver command periodically during the wait, or move the wait outside the session entirely and open a fresh session afterwards. Both keep the reaper armed, so a client that dies mid-wait is still cleaned up by the server.
  • Does the reaper firing look different in the client from a session you deleted yourself?
    Yes. After a reap the session id is gone from the server, so the next command comes back as an invalid-session error rather than as a timeout. That is why the symptom rarely mentions timeouts at all and is routinely misread as a driver or device crash.

The idle timer is a hotel's automatic checkout: leave the room untouched long enough and housekeeping clears it out. Turning it off means the room is only ever freed when somebody remembers to hand the key back.

saying these in an interview costs you the question

  • Thinking the timer measures test activity rather than gaps between commands
  • Assuming a crashed client still releases the session somehow
  • Believing zero costs nothing because the suite calls quit anyway
  • Treating the leftover as identical on Android and iOS
  • Disabling it instead of finding out why the client goes silent