fix(cli): scan a URL without a CDP browser instead of crashing - #77
Merged
Conversation
picket scan <url> sent every URL to captureFromBridge, which requires a
CDP endpoint. With none configured it handed puppeteer an undefined
browserURL and died on a bare assert stack:
Error: Exactly one of browserWSEndpoint, browserURL, transport or
channel must be passed to puppeteer.connect
That is the first command a new user runs, and
px @askalf/fieldpass scan
https://... could never work without a browser already running.
Now: URL + a browser (--browser, PICKET_BROWSER_URL, or PICKET_CDP) uses
the CDP path unchanged. URL with no browser is fetched and parsed
statically, and the header says so:
captured: static
note: static fetch - CSS-based hiding is not resolved.
Pass --browser <cdp-url> for computed styles.
The note is load-bearing, not decoration: the static parser cannot resolve
computed styles, so class-driven white-on-white and offscreen hiding read
as visible. Inline and structural hiding are still caught. Also surfaces
capturedBy in --json so a caller can tell which path ran, and turns fetch
failures into a message + exit 65 rather than a stack trace.
Verified: hostile fixture over https -> BLOCK exit 2; local file -> BLOCK
exit 2 (unchanged); https://example.com -> ALLOW exit 0 (no false
positive). Suite 146/146.
askalf
enabled auto-merge (squash)
August 10, 2026 21:11
sprayberry-redline
approved these changes
Aug 10, 2026
sprayberry-redline
left a comment
Collaborator
There was a problem hiding this comment.
This is an automated review from the Sprayberry Labs fleet code reviewer.
Verdict: No blocking issues found — clean, well-scoped fix.
What I checked
gh pr diff 77(the only changed file,bin/picket.mjs, +35/-4)captureFromHtml/captureFromBridgeinsrc/capture.mjsto confirmobs.capturedByis set on both paths ('static'and'cdp') before it's read atbin/picket.mjsfor the new--jsonfield and thecaptured: ${obs.capturedBy}log line- CI rollup: CodeQL,
test,test-node20allSUCCESSon heada6d4624
Findings
None blocking.
Minor: the PR body claims "Suite 146/146" but there is no pre-existing or new test file covering bin/picket.mjs (git log on that path shows only a dependency-bump commit before this PR, and no test/*.mjs references bin/picket). That's not a regression this PR introduces — the CLI entrypoint was already untested — but the new branch (no-CDP URL fetch, fetch failure → exit 65, --json capturedBy) is exactly the kind of logic that would benefit from a script-level test now that it's grown a failure path.
What's good
- The failure mode is exactly what the PR describes:
captureFromBridgewas called unconditionally for any URL, handing puppeteer anundefined browserURLand crashing with a raw stack trace — confirmed by reading the before/after diff of theisUrlbranch (bin/picket.mjsold line 32-33 vs newif (isUrl && browserURL) ... else if (isUrl) ...). - The static-fetch fallback correctly surfaces its own limitation instead of silently pretending to have full fidelity — the
note: static fetch — CSS-based hiding is not resolvedline is only printed whenstaticOnUrlis true, andcapturedByis threaded into--jsonoutput so callers can detect which path ran. - Fetch errors and non-2xx responses are caught and exit distinctly (65) rather than throwing, consistent with the rest of the CLI's exit-code contract described in the header comment.
- Header doc-comment update matches the new behavior precisely.
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.
The bug
picket scan <url>routed every URL tocaptureFromBridge, which needs a CDP endpoint. With none configured it handed puppeteer an undefinedbrowserURL:So
npx -y @askalf/fieldpass scan https://...— the first thing a new user tries, and the only zero-install entry point we have — could never work without a browser already running. Scanning a local file worked fine, which is why it went unnoticed.The fix
--browser,PICKET_BROWSER_URL, or now alsoPICKET_CDP) → CDP path, unchanged--jsonnow carriescapturedByso a caller can tell which path ranThe header states the weaker path plainly:
That note is load-bearing. The static parser can't resolve computed styles, so class-driven white-on-white and offscreen hiding read as visible — the CDP↔static gap that's already documented in the module. Inline and structural hiding are still caught, so the fixture below still reaches BLOCK, just with fewer nodes marked hidden.
Verified
https://example.comSuite 146/146.
Why now
Needed for a zero-install demo. Worth merging on its own regardless — it's the documented usage in the CLI's own header comment (
picket scan https://example.com) failing on a stack trace.