Part of #3607.
Motivation
captureShot() (src/review/visual/shot.ts) needs a truthy env.BROWSER binding to render anything. Self-host gets one via src/server.ts's process.env.BROWSER_WS_ENDPOINT ? { BROWSER: {} } : {} injection, and src/selfhost/stubs/puppeteer.ts (#980) connects that binding to an external browserless/chrome-compatible WebSocket endpoint instead of Cloudflare's binding API. None of this is deployed on edge-us-01 today: BROWSER_WS_ENDPOINT is unset, the running image wasn't built with INSTALL_VISUAL_REVIEW=true (so puppeteer-core isn't even installed), and there's no browserless/chrome (or compatible) container anywhere in the self-host docker-compose stack. This is the one missing piece between "the code already supports self-host visual capture" and "self-host visual capture actually works."
Requirements
- Add a
browserless/chrome (or equivalent, e.g. ghcr.io/browserless/chromium) service to the self-host docker-compose.yml (or an operator opt-in override/profile, matching the existing pattern for optional self-host services) — resource-bounded (real Chromium process; set sane memory/CPU limits) and internal-network-only, never publicly exposed, same posture as postgres/redis/qdrant today.
- Document
BROWSER_WS_ENDPOINT in .env.example/.env.selfhost.example and regenerate the self-host env-reference doc (npm run selfhost:env-reference).
- Rebuild the gittensory image with
--build-arg INSTALL_VISUAL_REVIEW=true. Verify the self-host stub degrades safely (not a confusing missing-module error) when the flag is off but the endpoint is set, or vice versa.
- Verify end-to-end on edge-us-01: with the service running + env configured + image rebuilt,
captureShot() against a real public URL actually returns a PNG, not null. Add this to scripts/smoke-selfhost.sh if not already covered.
- Document the expected memory/CPU footprint and confirm it doesn't destabilize the existing stack (postgres/redis/qdrant/ollama/observability already running on the same box).
Deliverables
Expected outcome
env.BROWSER is truthy and functional on self-host; captureShot() returns real PNGs instead of null, unblocking every downstream piece of the visual-capture pipeline for any self-hosted repo, not just metagraphed.
Effort
M — mostly infra/config + verification. No src/ business-logic changes expected; the code-level substitution already exists.
Part of #3607.
Motivation
captureShot()(src/review/visual/shot.ts) needs a truthyenv.BROWSERbinding to render anything. Self-host gets one viasrc/server.ts'sprocess.env.BROWSER_WS_ENDPOINT ? { BROWSER: {} } : {}injection, andsrc/selfhost/stubs/puppeteer.ts(#980) connects that binding to an external browserless/chrome-compatible WebSocket endpoint instead of Cloudflare's binding API. None of this is deployed on edge-us-01 today:BROWSER_WS_ENDPOINTis unset, the running image wasn't built withINSTALL_VISUAL_REVIEW=true(sopuppeteer-coreisn't even installed), and there's no browserless/chrome (or compatible) container anywhere in the self-host docker-compose stack. This is the one missing piece between "the code already supports self-host visual capture" and "self-host visual capture actually works."Requirements
browserless/chrome(or equivalent, e.g.ghcr.io/browserless/chromium) service to the self-hostdocker-compose.yml(or an operator opt-in override/profile, matching the existing pattern for optional self-host services) — resource-bounded (real Chromium process; set sane memory/CPU limits) and internal-network-only, never publicly exposed, same posture as postgres/redis/qdrant today.BROWSER_WS_ENDPOINTin.env.example/.env.selfhost.exampleand regenerate the self-host env-reference doc (npm run selfhost:env-reference).--build-arg INSTALL_VISUAL_REVIEW=true. Verify the self-host stub degrades safely (not a confusing missing-module error) when the flag is off but the endpoint is set, or vice versa.captureShot()against a real public URL actually returns a PNG, not null. Add this toscripts/smoke-selfhost.shif not already covered.Deliverables
docker-compose.yml(or an override) with a browserless/chromium service, internal-network-only, resource-bounded.env.example/.env.selfhost.exampledocumentBROWSER_WS_ENDPOINT; env-reference doc regeneratedINSTALL_VISUAL_REVIEW=truefor deployments that want visual reviewcaptureShot()returns a real PNG against a real self-host deploymentExpected outcome
env.BROWSERis truthy and functional on self-host;captureShot()returns real PNGs instead ofnull, unblocking every downstream piece of the visual-capture pipeline for any self-hosted repo, not just metagraphed.Effort
M — mostly infra/config + verification. No src/ business-logic changes expected; the code-level substitution already exists.