using-webctl

$npx mdskill add oaustegard/claude-skills/using-webctl

Inject proxy auth headers to enable headless Chrome automation.

  • Resolves HTTPS navigation failures when webctl tunneling breaks.
  • Depends on webctl CLI, Python, and authenticated HTTP proxy.
  • Executes automatically when user requests browser automation.
  • Delivers functional Chromium sessions through local forwarding proxy.
SKILL.md
.github/skills/using-webctlView on GitHub ↗
---
name: using-webctl
description: 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.
metadata:
  version: 0.0.1
---

# Using webctl in Claude.ai Containers

Browser automation using webctl CLI with automatic proxy authentication handling for Claude.ai's egress-controlled environment.

## When This Skill Applies

- User requests browser automation, web scraping, or page interaction
- webctl commands fail with `ERR_TUNNEL_CONNECTION_FAILED`
- Playwright/Chromium needs to work through authenticated HTTP proxy

## Core Problem

Claude.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.

## Solution

A local forwarding proxy (port 18080) intercepts Chromium connections and injects `Proxy-Authorization` headers before forwarding to the real egress proxy.

## Setup Procedure

### 1. Install webctl

```bash
pip install webctl --break-system-packages
webctl setup  # Downloads Chromium if needed
```

### 2. Deploy Auth Proxy Module

Copy `scripts/auth_proxy.py` to webctl's daemon directory:

```bash
cp /mnt/skills/user/using-webctl/scripts/auth_proxy.py \
   /usr/local/lib/python3.12/dist-packages/webctl/daemon/
```

### 3. Patch Session Manager

Apply this patch to `/usr/local/lib/python3.12/dist-packages/webctl/daemon/session_manager.py`:

Find the context creation block (around line 104):
```python
# Create context
context = await browser.new_context(
    storage_state=storage_state, viewport={"width": 1280, "height": 720}
)
```

Replace with:
```python
# Create context with proxy from env (with auth handling)
from .auth_proxy import get_local_proxy_url
proxy_url = get_local_proxy_url()
proxy_config = {"server": proxy_url} if proxy_url else None

context = await browser.new_context(
    storage_state=storage_state, 
    viewport={"width": 1280, "height": 720},
    proxy=proxy_config
)
```

### 4. Verify

```bash
webctl start --mode unattended
webctl --quiet navigate "https://github.com"
webctl snapshot --interactive-only --limit 10
webctl stop --daemon
```

## Quick Reference

### Session Management
```bash
webctl start --mode unattended    # Headless browser
webctl stop --daemon              # Full shutdown
webctl status                     # Current state + console error counts
```

### Navigation
```bash
webctl navigate "https://..."
webctl back / webctl forward / webctl reload
```

### Observation
```bash
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
```

### Interaction
```bash
webctl click 'role=button name~="Submit"'
webctl type 'role=textbox name~="Email"' "user@example.com"
webctl type 'role=textbox name~="Search"' "query" --submit
webctl select 'role=combobox name~="Country"' --label "Germany"
```

### Query Syntax
- `role=button` — By ARIA role
- `name~="partial"` — Contains (preferred, more robust)
- `name="exact"` — Exact match
- `nth=0` — Select first when multiple matches

### Wait Conditions
```bash
webctl wait network-idle
webctl wait 'exists:role=button name~="Continue"'
webctl wait 'url-contains:"/dashboard"'
```

## Troubleshooting

### ERR_TUNNEL_CONNECTION_FAILED
Auth proxy not loaded. Verify:
1. `auth_proxy.py` exists in webctl daemon directory
2. Session manager is patched
3. Restart daemon: `webctl stop --daemon && webctl start --mode unattended`

### Multiple matches error
Add specificity or use `nth=0`:
```bash
webctl click 'role=link name="Sign in" nth=0'
```

### Verify proxy is running
```bash
netstat -tlnp | grep 18080
```

## Limitations

- Patch is session-local (container resets clear it)
- Only allowed domains in network config are accessible
- No Firefox support (download blocked by egress policy)

## Domain Restrictions

Check `<network_configuration>` in system prompt for allowed domains. Common allowed: `*.github.com`, `*.bsky.app`, allowed API endpoints.

## Output Filtering

Reduce context consumption:
```bash
webctl snapshot --interactive-only --limit 30    # Cap elements
webctl snapshot | grep -i "submit"               # Unix filtering
webctl --quiet navigate "..."                    # Suppress events
```
More from oaustegard/claude-skills