fix(flow): let a flow launch an app that can never be instrumented - #677
Open
filip131311 wants to merge 1 commit into
Open
fix(flow): let a flow launch an app that can never be instrumented#677filip131311 wants to merge 1 commit into
filip131311 wants to merge 1 commit into
Conversation
A flow's launch step waits for native devtools on iOS, and an Apple system app
is a platform binary with library validation, so that connection can never
happen. The step burned the full 8s timeout, failed, and took the rest of the
flow with it — even when nothing in the flow needed the view hierarchy. The
advice it gave ("re-run", "restart the argent server") could never work, which
is the retry loop #453 set out to remove.
The signal was already there: isInjectableBundleId, used by
native-devtools-status and by describe, whose own docs call injectable:false a
terminal state. The gate never consulted it. It does now, and the launch passes
with a warning saying what does and does not work for such an app.
Removing the wait exposes something the wait was accidentally hiding, so the
same change has to close it. Selector steps reach resolveNativeTargetApp, which
auto-targets whatever app is connected and accepts one reporting "inactive" as
foreground-like. An app the user was driving reports exactly that while it
backgrounds. Today the first tree read lands ~9.5s after launch, past that
window; without the wait it lands at ~1.5s, inside it — so a selector could
resolve against a different app's tree, and a hidden assertion against it would
pass. The run now records that the launched app cannot be instrumented and
fails tree reads immediately with a terminal reason.
The guard sits on settleTree and waitForCondition rather than on step kinds, so
it covers tap, long-press, await, assert, scroll-to and snapshot at once, and
leaves selector-less pinch and rotate working — those degrade to a default
aspect and never read a tree.
Reporting a pass rather than a failure is deliberate: the launch did succeed,
and the control case in the issue proves the rest of such a flow runs fine. The
warning field was already rendered by both the CLI and MCP as legacy
wire-compat; it is now produced, so those comments no longer describe it as
something only an old server sends.
The recording walkthrough in the create-flow skill used com.apple.Preferences
with selector steps, which could never have been captured or replayed. It now
uses a third-party app.
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.
Fixes #623.
Reproduced
Control — the identical flow without the launch step passes (
ok=true). So the runner was fine; the gate alone made system apps undriveable, and it gave advice that can never work for them.The signal already existed:
isInjectableBundleIdis used bynative-devtools-status(whose docs callinjectable: falseterminal — do NOT restart/retry) and bydescribe. The flow gate never consulted it, so it burned the full 8 sNATIVE_READY_TIMEOUT_MSwaiting for a connection that cannot occur.After:
Removing the wait exposes a race the wait was hiding — so this closes it too
This is the part I'd want a reviewer to look at hardest.
Selector steps reach
resolveNativeTargetApp, which auto-targets whatever app is connected.chooseFrontmostConnectedApphas a weak tier acceptingapplicationState === "inactive"— exactly what an app reports while it backgrounds. Today the first tree read lands ~9.5 s after launch (8 s gate + 1.5 s settle), comfortably past that window. Without the wait it lands at ~1.5 s, inside it.So a selector step could resolve against a different app's tree. Most conditions then fail noisily, but
hidden-shaped ones (assert: { hidden: X },await: { hidden: X }, awhen: { hidden: X }guard reporting a green skip) would pass. The fix records that the launched app cannot be instrumented and fails tree reads immediately with a terminal reason, which closes it.Without that guard the later path was also worse than the gate it replaced:
NATIVE_TARGET_NO_CONNECTED_APPSsays "Launch or restart the app first", after a 3 s settle.Design choices
Guard on tree reads, not step kinds —
settleTree+waitForCondition. That coverstap/long-press/await/assert/scroll-to/snapshotin two places, and leaves selector-lesspinch/rotateworking (they degrade to a default aspect and never read a tree). Guarding by step kind would have missedscroll-to, which callssettleTreedirectly.No pre-flight "does this flow use selectors" scan.
launchcan appear anywhere, including inside a nested fragment, andrun:fragments are read lazily at execution time — so "does this flow contain selectors" is ill-posed (which steps? after this launch? under awhen:that may not be entered?).Pass, not fail. The launch genuinely succeeded — the issue's own control proves the rest of such a flow runs. Failing it would re-introduce the bug for coordinate flows.
No 5th status. The CLI maps status with no fallback, so an unknown value renders
undefined. Used the existingwarningfield instead: ⚠ replaces the pass glyph, the text prints under the step, and it's counted in the summary. Both renderers already handle it as legacy wire-compat, so an old CLI renders it correctly — those comments are updated, since it's now actually produced.Also fixed
The create-flow skill's recording walkthrough used
com.apple.Preferenceswith selector steps (tap: { text: General }). That could never have been captured (selector capture reads the same tree) or replayed. Switched to a third-party app.Not fixed, deliberately
A fragment with no
launchstep, run while a system app is frontmost, never sets the flag and still gets the raw message. That's the issue's own control case.Checks
flow-composition.test.ts:213, which pins the retry-worded failure for a genuinely injectable app.