script-utils/linters/eslint-rules/no-over-cap-wait-in-eval-in-obsidian
ESLint rule: no-over-cap-wait-in-eval-in-obsidian
Reports an evalInObsidian / pollInObsidian closure that declares more waiting than the transport can ever honour.
A single closure is evaluated through one transport call, and that call is capped at roughly 30 seconds — Appium surfaces the cap as a bare WebDriverError: script timeout naming only AppiumTransport.evaluate, CDP as a command timeout. Neither names the wait that actually blew the budget, so the failure reads as a broken device rather than as a closure asking for more time than exists. That misdiagnosis cost T796 two days.
A closure declaring a 30 000 ms waitUntil, or two 20 000 ms ones, therefore cannot succeed on any machine slow enough to need the time it asks for. The fix is to move the waiting to Node: pollInObsidian runs a SHORT DOM-reading poll closure repeatedly, evaluates until in Node, and carries the long budget in timeoutInMilliseconds, so no single transport call is ever long.
WHAT COUNTS toward a closure’s budget, summed lexically: every waitUntil inside it — taking the helper’s documented 5 000 ms default when timeoutInMilliseconds is omitted, which is what makes several small sibling waits add up honestly — plus every sleep(n) settle. Waits inside a helper declared within the closure count too; the enclosing closure is found by walking outwards, so a nested function attributes to the closure it runs inside.
WHAT DOES NOT. A budget that cannot be resolved to a number contributes nothing and is never reported (owner, 2026-09-09). Scope analysis resolves the shapes that actually occur — a literal, a const in any enclosing scope, and a destructured callback parameter followed back through the call’s own input object — and resolving through scope rather than by regex is the whole reason this is a rule and not a script. But a value arriving from a caller (options.timeout ?? DEFAULT, threaded through a parameter) would need analysis across call boundaries, and reporting every such site would make each legitimately parameterized helper red across thirty repos. A LOOP is out of scope for the same reason: a sleep in a while has no statically declared ceiling, so the sum below is per-iteration and says nothing about the whole.
Not every over-cap closure can become a poll / until pair, so the rule is meant to be disabled — with a reason — at the sites that have one. require-description makes that reason mandatory, which is the point: it turns an invisible assumption into a written one.
Variables
Section titled “Variables”| Variable | Description |
|---|---|
| MESSAGE_ID | Message ID reported when an in-Obsidian closure declares more waiting than the transport’s cap allows. |
| noOverCapWaitInEvalInObsidian | ESLint rule disallowing an in-Obsidian closure whose declared waiting exceeds the transport’s script-timeout cap. |