Skip to content

Fix Playwright cleanup crash, CDP port conflicts, localhost IPv6, and URL null-string pollution#70

Open
andrii-mazurchuk wants to merge 2 commits into
Pickle-Pixel:mainfrom
andrii-mazurchuk:main
Open

Fix Playwright cleanup crash, CDP port conflicts, localhost IPv6, and URL null-string pollution#70
andrii-mazurchuk wants to merge 2 commits into
Pickle-Pixel:mainfrom
andrii-mazurchuk:main

Conversation

@andrii-mazurchuk

Copy link
Copy Markdown

Summary

Four operational bugs found while running ApplyPilot on Ubuntu 26.04 with multiple concurrent instances.

1. Enrich stage crashes on exit when Chrome launch fails (detail.py)

Problem: When Playwright fails to launch Chrome mid-batch (e.g. because the system Chrome binary is unavailable), the cleanup path of with sync_playwright() as p: raises 'PlaywrightContextManager' object has no attribute '_playwright', propagating an unhandled exception that kills the entire enrich stage with exit 1.

Fix: Wrap the with sync_playwright() as p: block in a broad except Exception at the batch level. Any jobs that hadn't been processed yet are written to the DB as browser_unavailable so they can be retried, and the stage exits cleanly.


2. CDP endpoint uses localhost which resolves to IPv6 on Ubuntu 26.04 (launcher.py)

Problem: http://localhost:{port} in _make_mcp_config() resolves to ::1 (IPv6) on Ubuntu 26.04, but Chrome's remote debugging port only binds to 127.0.0.1 (IPv4). Result: ECONNREFUSED ::1:9222 on every apply attempt.

Fix: Replace localhost with 127.0.0.1 in the CDP endpoint URL.


3. Concurrent apply runs on multiple instances share port 9222 (chrome.py)

Problem: All instances hard-code BASE_CDP_PORT = 9222. When two instances run apply concurrently, one instance's _kill_on_port(9222) kills the other instance's Chrome mid-session.

Fix: Derive a stable per-instance base port from the MD5 hash of the instance directory name, mapping each instance into a unique 10-port window in the range 9200–9399. Deterministic: same instance always gets the same port.

def _instance_base_port() -> int:
    instance_dir = os.environ.get("APPLYPILOT_DIR", "")
    name = Path(instance_dir).name if instance_dir else "default"
    offset = int(hashlib.md5(name.encode()).hexdigest()[:2], 16) % 20 * 10
    return 9200 + offset

4. LLM returns "None" string for missing application_url (detail.py, launcher.py, prompt.py)

Problem: When no apply URL is found, the LLM returns the string "None" rather than JSON null. Python's or operator treats non-empty strings as truthy, so application_url = "None" passes all guards and gets stored in the DB and shown to the apply agent as a valid URL (URL: None), causing the agent to refuse to proceed.

Fix:

  • detail.py: sanitize LLM output before writing — treat "None", "null", "N/A", "" as NULL
  • launcher.py: guard apply_url construction with explicit != "None" check
  • prompt.py: same guard when building the URL line shown to the apply agent

5. Site-specific password support (prompt.py)

Problem: Different job sites may require different account credentials but the profile only supports one global password.

Fix: Check personal.get('site_passwords', {}).get(job_site, personal.get('password')) so per-site passwords in profile.json take precedence over the default.

Test environment

  • Ubuntu 26.04, system Chrome (Playwright bundled Chromium not supported on this kernel)
  • Multiple concurrent instances via APPLYPILOT_DIR
  • Gemini 2.0 Flash as LLM

Checklist

  • Fixes are isolated to the affected files
  • No new dependencies introduced
  • Backwards compatible — existing single-instance setups unaffected

andrii-mazurchuk and others added 2 commits June 23, 2026 21:30
Line endings: all Python source files converted from CRLF to LF.

Bug fixes (detail.py, chrome.py, launcher.py, prompt.py):
- enrich: catch Playwright cleanup crash on Ubuntu 26.04 when Chrome
  launch fails mid-batch; unprocessed jobs marked browser_unavailable
- enrich: sanitize LLM-returned application_url — treat "None"/"null"
  strings as NULL, not as a valid URL
- apply/chrome: derive stable per-instance CDP base port from MD5 hash
  of instance name so concurrent apply runs don't clobber each other
- apply/launcher: use 127.0.0.1 instead of localhost for CDP endpoint
  (Ubuntu 26.04 resolves localhost to IPv6 ::1, Chrome binds IPv4 only)
- apply/launcher: guard application_url against "None" string value
- apply/prompt: look up site-specific password from profile.json
  personal.site_passwords before falling back to the default password

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant