using-webctl/SKILL.md
Browser automation via webctl CLI in Claude.ai containers with authenticated proxy support. Use when users mention webctl, browser automation, Playwright browsing, web scraping, or headless Chrome in container environments.
npx skillsauth add oaustegard/claude-skills using-webctlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Browser automation using webctl CLI with automatic proxy authentication handling for Claude.ai's egress-controlled environment.
ERR_TUNNEL_CONNECTION_FAILEDClaude.ai containers route traffic through an authenticated egress proxy (HTTP_PROXY env var with JWT credentials). Chromium doesn't properly handle proxy authentication for HTTPS CONNECT tunnels, causing all HTTPS navigation to fail even though curl works.
A local forwarding proxy (port 18080) intercepts Chromium connections and injects Proxy-Authorization headers before forwarding to the real egress proxy.
pip install webctl --break-system-packages
webctl setup # Downloads Chromium if needed
Copy scripts/auth_proxy.py to webctl's daemon directory:
cp /mnt/skills/user/using-webctl/scripts/auth_proxy.py \
/usr/local/lib/python3.12/dist-packages/webctl/daemon/
Apply this patch to /usr/local/lib/python3.12/dist-packages/webctl/daemon/session_manager.py.
Recent webctl versions split context creation into two branches (mobile emulation vs desktop). Both must receive the proxy config. Find the block (search for cfg = WebctlConfig.load() followed by the if mode == "unattended" and cfg.mobile_emulation: branch — around line 190):
cfg = WebctlConfig.load()
if mode == "unattended" and cfg.mobile_emulation:
# Mobile emulation: cleaner pages, fewer ads, simpler layouts
device = self._playwright.devices["Pixel 7"]
context = await browser.new_context(
storage_state=storage_state,
**device,
)
else:
context = await browser.new_context(
storage_state=storage_state, viewport={"width": 1280, "height": 720}
)
Replace with:
cfg = WebctlConfig.load()
from .auth_proxy import get_local_proxy_url
proxy_url = get_local_proxy_url()
proxy_config = {"server": proxy_url} if proxy_url else None
if mode == "unattended" and cfg.mobile_emulation:
# Mobile emulation: cleaner pages, fewer ads, simpler layouts
device = self._playwright.devices["Pixel 7"]
context = await browser.new_context(
storage_state=storage_state,
proxy=proxy_config,
**device,
)
else:
context = await browser.new_context(
storage_state=storage_state, viewport={"width": 1280, "height": 720},
proxy=proxy_config
)
If the file only has a single new_context call (older webctl), inject proxy=proxy_config into that one and place the three proxy_url lines just above it.
webctl start --mode unattended
webctl --quiet navigate "https://github.com"
webctl snapshot --interactive-only --limit 10
webctl stop
webctl start --mode unattended # Headless browser
webctl stop # Full shutdown (use --keep-daemon to leave daemon running)
webctl status # Current state + console error counts
webctl navigate "https://..."
webctl back / webctl forward / webctl reload
webctl snapshot --interactive-only --limit 30 # Buttons, links, inputs
webctl snapshot --within "role=main" # Scope to container
webctl query "role=button name~=Submit" # Debug queries
webctl screenshot --path shot.png
webctl click 'role=button name~="Submit"'
webctl type 'role=textbox name~="Email"' "[email protected]"
webctl type 'role=textbox name~="Search"' "query" --submit
webctl select 'role=combobox name~="Country"' --label "Germany"
role=button — By ARIA rolename~="partial" — Contains (preferred, more robust)name="exact" — Exact matchnth=0 — Select first when multiple matcheswebctl wait network-idle
webctl wait 'exists:role=button name~="Continue"'
webctl wait 'url-contains:"/dashboard"'
Auth proxy not loaded. Verify:
auth_proxy.py exists in webctl daemon directorywebctl stop && webctl start --mode unattendedAdd specificity or use nth=0:
webctl click 'role=link name="Sign in" nth=0'
netstat -tlnp | grep 18080
Check <network_configuration> in system prompt for allowed domains. Common allowed: *.github.com, *.bsky.app, allowed API endpoints.
Reduce context consumption:
webctl snapshot --interactive-only --limit 30 # Cap elements
webctl snapshot | grep -i "submit" # Unix filtering
webctl --quiet navigate "..." # Suppress events
development
--- name: verifying-claims description: Check that a document's claims about code are actually true by reading the prose, the code, and the tests and reporting (or fixing) where they disagree. Use whenever the user wants to verify a README, guide, spec, or docstring still matches the code; whenever they mention documentation drift, doc-code sync, "is this still accurate", stale docs, or keeping docs/tests/code consistent; before publishing or merging a docs change; or as a periodic doc-accuracy
tools
Query, filter, and transform Markdown structurally with mq — a jq-like CLI for Markdown. Use to extract headings/sections/code-blocks/links from .md files, build a table of contents, pull code blocks of a given language, slice or reshape LLM prompt/output Markdown, or batch-transform docs. Triggers on "extract sections from this markdown", "get all the code blocks", "jq for markdown", "mq", or any structural query over Markdown that grep/Read can't do cleanly.
development
Composes single-file HTML artifacts (PR review writeups, status reports, incident postmortems, slide decks, design systems, prototypes, flowcharts, module maps, feature explainers, kanban boards, prompt tuners) from a small JSON spec instead of hand-written HTML/CSS/JS. Use when the user asks to "compare options side-by-side", requests an HTML version of a report or review or deck, asks for a flowchart, status update, postmortem, design system reference, interactive prototype, custom editor — or explicitly says "HTML artifact", "single HTML file", "self-contained HTML". Skip for ad-hoc HTML snippets (forms, emails, embedded widgets) where there's no template fit.
development
DAG workflow runner that encodes control flow in code, not prose. Use when a procedure has 3+ steps with branching, retries, or validation that must be enforced — gates as `when=`, edge contracts as `validate=`, predicate loops as `retry_until=`. The runner owns the graph; the LLM provides leaves. Also covers parallel execution, checkpoint resume, detached side-effects.