await-ui-element silently drops unknown params, so a misspelled textMatch turns a failing wait into success

Open Beginner friendly
#997 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
86/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
tooling

Research direction

Start in packages/tool-server/src/tools/await-ui-element/index.ts:97-152 and inspect the top-level schema alongside its strict nested selector. Run the provided safeParse and textMatches reproductions against the built module, then verify that misspelled or unknown top-level keys are rejected while valid textMatch behavior remains unchanged, including through run-sequence.

Written by the indexing model from the issue text.

Description

b:minor bug

[Diplomat, Opus 5]: await-ui-element's top-level parameter object is not .strict(), so a misspelled textMatch is dropped without a word and the wait silently falls back to contains - turning a check that should fail into success: true.

Found while reviewing #720 (which does not cause this - the schema shape is unchanged from main), so filing separately.

Repro

packages/tool-server/src/tools/await-ui-element/index.ts:97-152 builds z.object({...}).refine(...) with no .strict(). Against the built module:

const s = createAwaitUiElementTool({}).zodSchema;
const base = { udid: 'x', selector: { identifier: 'cgj' }, condition: 'text', expectedText: 'Save' };

s.safeParse({ ...base, textMatch:  'equals' })  accepted=true   effective textMatch="equals"
s.safeParse({ ...base, textMatches:'equals' })  accepted=true   effective textMatch=undefined
s.safeParse({ ...base, text_match: 'equals' })  accepted=true   effective textMatch=undefined
s.safeParse({ ...base, TextMatch:  'equals' })  accepted=true   effective textMatch=undefined
s.safeParse({ ...base, frobnicate: 1        })  accepted=true   effective textMatch=undefined

undefined is the default, which is contains, so the comparator changes under the caller:

const label = 'Save' + String.fromCodePoint(0x34F) + 'Changes';
textMatches(label, 'Save', 'equals')     false
textMatches(label, 'Save', undefined)    true      // what the dropped key leaves

So a caller that asked to pin the whole string gets a substring check and is told the wait passed.

Why this is worth fixing

Every nearest twin rejects the same slip loudly:

  • the nested selector in the same call is strict - { selector: { identifier: 'cgj', withn: {} } } gives Unrecognized key: "withn"
  • the flow YAML parser (flow-utils.ts rejectUnknownKeys) answers selector has unknown key `wthin` (did you mean `within`?)
  • three other tool schemas in this package already use .strict() (screenshot-diff, stop-all-simulator-servers, flow-device)

textMatches is also a real key name elsewhere in the same module (Selector.textMatches), so it is a particularly easy slip for an agent to make, and the failure mode is a false pass rather than an error.

The same hole is reachable through run-sequence, which allowlists this tool.

Dominant language
TypeScript
Stars
2.9k
Forks
118
Avg merge
1d 19h
Merged PRs (30d)
93

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from software-mansion/argent

All issues in software-mansion/argent

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.