You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
extract() leaked accessibility-tree IDs ([0-74], 0-74) when the schema used plain z.string() for URL fields. injectUrls only ran for z.url() / format: uri.
Agents and callers that extract links as strings (for example { stories: z.string() } or { rank1_url: z.string(), ... } on a page like Hacker News) got raw element IDs back. z.url() fields already resolved correctly.
Fix: tell the model to return frameId-backendId only for URL values, then replaceElementIdsWithUrls maps leaked IDs through combinedUrlMap. Bracketed IDs rewrite in any string; bare IDs only on URL-ish field names.
pnpm exec vitest run --root . packages/extension/tests/extract.test.ts
extract() on a page with links using z.object({ title: z.string(), url: z.url() }) still returns real hrefs
Same instruction with z.object({ title: z.string(), url: z.string() }) returns real hrefs, not element IDs
extract("Extract the first 5 story URLs", z.object({ urls: z.string() })) returns real URLs, not [0-74] / 0-74
Non-URL strings that look like IDs (e.g. score: "0-74") are left unchanged
Summary by cubic
Resolves leaked accessibility-tree IDs from extract() when URL fields use z.string(). Before, only z.url() fields resolved to hrefs; now both string and z.url() URL fields return real URLs, and non-URL fields never return element IDs.
Updates the system prompt: for any value that should be a URL, return the link element ID as frameId-backendId (no brackets); for all other fields, return only visible text and never return IDs.
Adds replaceElementIdsWithUrls in the extraction flow to map leaked IDs to hrefs via the snapshot combinedUrlMap. Bracketed IDs ([0-74]) are replaced anywhere; bare IDs (0-74) are replaced only on URL-like field names (url, href, link, uri). Unknown IDs are left as-is.
Extends transformSchema to treat url, uri, and uri-reference as URL formats, preserving injectUrls behavior for typed z.url() fields.
Tests cover prompt wording and end-to-end resolution for z.string() URL outputs.
No API changes. Migration: if any caller relied on raw element IDs in string fields, update that logic to accept real URLs.
Written for commit e9bf389. Summary will update on new commits.
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run.
Approving the latest commit mirrors it into an internal PR owned by the approver.
If new commits are pushed later, the internal PR stays open but is marked stale until someone approves the latest external commit and refreshes it.
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
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.
Summary
extract()leaked accessibility-tree IDs ([0-74],0-74) when the schema used plainz.string()for URL fields.injectUrlsonly ran forz.url()/format: uri.{ stories: z.string() }or{ rank1_url: z.string(), ... }on a page like Hacker News) got raw element IDs back.z.url()fields already resolved correctly.frameId-backendIdonly for URL values, thenreplaceElementIdsWithUrlsmaps leaked IDs throughcombinedUrlMap. Bracketed IDs rewrite in any string; bare IDs only on URL-ish field names.Before
{ "stories": "[0-74]\n[0-112]\n[0-150]\n[0-188]\n[0-226]" }
{
"rank1_title": "GrapheneOS in 2027 available on high-end Motorola phones",
"rank1_url": "0-74",
"rank2_url": "0-112",
"rank3_url": "0-150",
"rank4_url": "0-188",
"rank5_url": "0-226"
}
After (this fix)
{
"stories": "https://grapheneos.social/@GrapheneOS/117078064184215730\nhttps://yassa9.github.io/osint/gralhix-004/\nhttps://www.raphaelbauer.com/posts/postgresql-everything/\nhttps://sprocketfox.io/xssfox/2026/08/19/sondehub-and-war/\nhttps://openlogi.org/en"
}
{
"rank1_title": "GrapheneOS in 2027 available on high-end Motorola phones",
"rank1_url": "https://grapheneos.social/@GrapheneOS/117078064184215730",
"rank2_url": "https://yassa9.github.io/osint/gralhix-004/",
"rank3_url": "https://www.raphaelbauer.com/posts/postgresql-everything/",
"rank4_url": "https://sprocketfox.io/xssfox/2026/08/19/sondehub-and-war/",
"rank5_url": "https://openlogi.org/en"
}
Test plan
pnpm exec vitest run --root . packages/extension/tests/extract.test.tsextract()on a page with links usingz.object({ title: z.string(), url: z.url() })still returns real hrefsz.object({ title: z.string(), url: z.string() })returns real hrefs, not element IDsextract("Extract the first 5 story URLs", z.object({ urls: z.string() }))returns real URLs, not[0-74]/0-74score: "0-74") are left unchangedSummary by cubic
Resolves leaked accessibility-tree IDs from extract() when URL fields use z.string(). Before, only z.url() fields resolved to hrefs; now both string and z.url() URL fields return real URLs, and non-URL fields never return element IDs.
Written for commit e9bf389. Summary will update on new commits.