---
name: chrome_load_extensions
kind: function
lang: bash
domain: browser
version: "1.0.0"
purity: impure
signature: "chrome_load_extensions [--port N] [--profile DIR] --ext PATH [--ext PATH ...] [--proxy URL] [--url URL]"
description: "Lanza Chrome con extensiones unpacked via --load-extension (WSL2→Windows chrome.exe, paths traducidos, join sin echo, setsid anti-exit-144). OJO: --load-extension SOLO funciona en Chrome for Testing/Chromium/Dev. En Chrome STABLE 138+ esta DESACTIVADO (feature DisableLoadExtensionCommandLineSwitch + bloqueo duro en 148) y carga 0 extensiones aunque el cmdline sea correcto. Para Chrome stable usar install via Web Store (1-clic, persiste en perfil) o enterprise policy ExtensionInstallForcelist (requiere HKLM/HKCU Policies escribible — denegado en maquinas gestionadas)."
tags: [chrome, cdp, browser, extensions, wsl2, navegator]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: false
tests: []
test_file_path: ""
params:
  - name: "--port N"
    desc: "Puerto de remote debugging CDP. Default: 9222."
  - name: "--profile DIR"
    desc: "Chrome user-data-dir. Acepta ruta Windows (C:\\...) o ruta WSL/Linux (se traduce via wslpath -w). Default: C:\\Users\\<USERNAME>\\AppData\\Local\\fn-chrome-cdp-profile (WSL2) o /tmp/fn-chrome-cdp-profile (Linux nativo)."
  - name: "--ext PATH"
    desc: "Ruta a un directorio de extensión unpacked. Repetible. Acepta ruta Windows (se pasa intacta) o ruta WSL/Linux (se traduce via wslpath -w). Obligatorio al menos uno."
  - name: "--proxy URL"
    desc: "Proxy opcional, ej. http://127.0.0.1:8889. Agrega --proxy-server=URL a Chrome."
  - name: "--url URL"
    desc: "URL inicial opcional para abrir con --new-window."
output: "PID del proceso Chrome lanzado (stdout). Mensajes de estado en stderr. CDP listo en 127.0.0.1:<port>."
file_path: "bash/functions/browser/chrome_load_extensions.sh"
---

## Ejemplo

```bash
source bash/functions/browser/chrome_load_extensions.sh

chrome_load_extensions \
  --port 9222 \
  --profile 'C:\Users\lucas\AppData\Local\fn-chrome-cdp-profile' \
  --ext 'C:\Users\lucas\hls-dl-ext' \
  --ext 'C:\Users\lucas\ubol' \
  --proxy http://127.0.0.1:8889 \
  --url https://www.gnularetro.cc/
```

Sin proxy ni URL, sólo extensiones:

```bash
source bash/functions/browser/chrome_load_extensions.sh

pid=$(chrome_load_extensions \
  --ext '/home/lucas/dev/hls-dl-ext' \
  --ext '/home/lucas/dev/ubol')
# Paths WSL traducidos automáticamente a Windows.
# CDP listo en 127.0.0.1:9222.
echo "Chrome PID: $pid"
```

## Cuando usarla

Cuando necesites Chrome CDP con extensiones unpacked cargadas (HLS downloader, uBlock Origin, extensiones en desarrollo) y `chrome_launch_go_browser` no sirve porque hardcodea `--disable-extensions`. WSL2→Windows. Ideal para sesiones de navegator con proxy + extensión activa.

## Gotchas

- **MUERTO en Chrome STABLE 138+ (validado 2026-05-30, Chrome 148)**: `--load-extension` NO carga nada en el canal stable, ni con `--disable-extensions-except` ni con `--disable-features=DisableLoadExtensionCommandLineSwitch`. `chrome://version` muestra el flag correcto pero `chrome://extensions` sale vacío. Google lo bloqueó duro en stable. La función SOLO sirve en **Chrome for Testing / Chromium / Dev/Canary**, donde el switch sigue activo. Para stable: ver opciones abajo.
- **Instalar en Chrome STABLE (las que SÍ funcionan)**:
  1. **Web Store 1-clic** — abre la página del store en el perfil CDP, el humano da "Añadir a Chrome". Persiste en el perfil para siempre (futuros lanzamientos ya con la extensión, sin flags). El popup de confirmación es UI del navegador (no DOM) → NO es CDP-clickable, requiere gesto humano. Único método no-admin que persiste por-perfil.
  2. **Enterprise policy** `ExtensionInstallForcelist` (HKCU/HKLM `\Software\Policies\Google\Chrome`) — force-install sin clic desde el store, browser-wide. El key `Policies\Google\Chrome` puede dar "Access denied" al escribir (visto 2026-05-30 incluso en máquina personal vía reg.exe/PowerShell desde WSL — Chrome/Windows protege el subárbol Policies). Si funciona, requiere relanzar Chrome para que descargue del store. Método global (afecta todos los perfiles).
  3. Extensiones **unpacked custom** (no en store, ej. un HLS downloader propio) en stable: no hay vía no-admin. Empaquetar a CRX + self-host `update_url` + policy, o usar Chrome for Testing. A menudo innecesario si la lógica vive fuera (ej. `grab_stream.py` descarga sin extensión).
- **Combo flags (solo Chrome for Testing/dev)**: requiere AMBOS `--load-extension=p1,p2` Y `--disable-extensions-except=p1,p2` juntos + `--disable-features=DisableLoadExtensionCommandLineSwitch`. **NUNCA `--disable-extensions`** (desactiva todo).
- **join sin `echo`**: rutas Windows `C:\Users\...` tienen `\U`; el `echo` de zsh (o sh con xpg_echo) lo interpreta como escape unicode y trunca la ruta a `C:`. La función usa acumulador `+=`, no `echo`. Verificable en `chrome://version` (debe verse el path completo, no `--load-extension=C:`).
- **exit 144 en Bash tool**: si el proceso Chrome retiene el pipe stdout, la herramienta devuelve exit 144. Esta función lanza con `setsid ... </dev/null >log 2>&1 &` + `disown` para desacoplar completamente. El log queda en `/tmp/chrome_ext_<port>.log`.
- **WSL2: traducir paths con `wslpath -w`**: los paths de `--ext` y `--profile` que sean rutas Linux se traducen automáticamente. Las rutas Windows (`C:\...`) se pasan intactas. `wslpath` debe estar disponible (estándar en WSL2 desde Windows 10 1903+).
- **Perfil ya abierto**: si Chrome ya tiene ese perfil abierto, relanzar añade una ventana extra a la misma instancia. La función detecta si CDP ya responde en el puerto y avisa por stderr, pero procede igualmente.
- **Web Store vs unpacked**: instalar extensiones desde la Web Store (un clic) persiste en el perfil sin necesidad de flags y sobrevive reinicios. Esta función es para extensiones unpacked en desarrollo o que no están en la Web Store. Si usas ambas, los flags no interfieren con las instaladas del store.
- **zsh globbing**: `--remote-allow-origins=*` está dentro de comillas en la función, no se expande. Si lo pasas desde la línea de comandos, entrecomillarlo.
- **Proxy + extensión**: si usas proxy para captura de tráfico (Burp, mitmproxy, gost), el proxy se aplica a toda la sesión Chrome, incluyendo el tráfico de las extensiones.
