Skip to content

fix: make camofox-browser usable on bundled Firefox 135.0.1#6017

Merged
skyfallsin merged 3 commits into
jo-inc:masterfrom
lindenau-cedix:fix/launcher-await-shm-playwright-pin
Jul 20, 2026
Merged

fix: make camofox-browser usable on bundled Firefox 135.0.1#6017
skyfallsin merged 3 commits into
jo-inc:masterfrom
lindenau-cedix:fix/launcher-await-shm-playwright-pin

Conversation

@lindenau-cedix

Copy link
Copy Markdown
Contributor

Summary

A fresh make up of @askjo/camofox-browser@1.11.2 starts the server (HTTP 200 on /health) but every POST /tabs returns 500. Three independent bugs compound; this PR fixes all three.

$ curl -s -X POST http://localhost:9377/tabs \
    -H 'content-type: application/json' \
    -d '{"userId":"u","sessionKey":"k","url":"https://example.com"}'
{"error":"Internal server error"}

After this PR: HTTP 200, real tabId, GET /tabs/{tabId}/snapshot?userId=... returns the accessibility tree, DELETE /tabs/{tabId}?userId=... returns {"ok":true}.

Changes

# File What Why
1 server.js:955 vdDisplay = localVirtualDisplay.get();vdDisplay = await localVirtualDisplay.get(); VirtualDisplay.get() is async. Without await, the Promise object is stored, then coerced to the string "[object Promise]" and set as DISPLAY. Firefox exits with Error: cannot open display: [object Promise]. The log line "xvfb virtual display started","display":{} is the diagnostic giveaway (JSON.stringify on a Promise produces {}).
2 package.json:137 "playwright-core": "^1.58.0""playwright-core": "1.51.0" ^1.58.0 resolves to 1.61.0, which sends viewport.isMobile: false to Firefox's Browser.setDefaultViewport CDP method. Firefox 135.0.1 (pinned via CAMOUFOX_VERSION) does not describe that field, so newContext() throws: "Found property <root>.viewport.isMobile - false which is not described in this scheme". 1.51.0 is the playwright version contemporary with Firefox 135.x.
3 Makefile:67 docker run ... -p 9377:9377 $(IMAGE)docker run ... --shm-size=2g -p 9377:9377 $(IMAGE) The default Compose /dev/shm is 64 MiB, too small for Firefox to map shared memory; the browser process crashes silently after launch. --shm-size=2g is the upstream best-practice for Playwright + Firefox in containers.

Repro

git clone https://github.com/jo-inc/camofox-browser && cd camofox-browser
make down && make build && make up
sleep 5
curl -s -X POST http://localhost:9377/tabs \
  -H 'content-type: application/json' \
  -d '{"userId":"u","sessionKey":"k","url":"https://example.com"}'
# before this PR: {"error":"Internal server error"}
# after this PR:  {"tabId":"...","url":"https://example.com/"}

Container logs to watch for each fix:

docker logs camofox-browser | grep -E 'cannot open|isMobile|object Promise'

Note on the playwright pin

The playwright-core: 1.51.0 pin is exact, not a range, and is matched to the bundled Camoufox/Firefox version. The two need to move together:

  • When CAMOUFOX_VERSION in the Dockerfile is bumped, the playwright-core pin in package.json needs to move to the playwright version released in the same window. The reference is the Playwright release notes — each version names its bundled Firefox.
  • Long-term, the project should either bump CAMOUFOX_VERSION to match a playwright that works with ^1.58.0, or document the version pairing in a renovate.json / dependabot.yml config so the pins move together.

Test plan

  • make down && make build && make up — clean rebuild
  • GET /health returns {"ok":true,"engine":"camoufox","browserConnected":true,"browserRunning":true,...}
  • POST /tabs with example.com returns HTTP 200 + tabId
  • GET /tabs/{tabId}/snapshot?userId=... returns the accessibility tree (heading "Example Domain", link "Learn more" [e1])
  • DELETE /tabs/{tabId}?userId=... returns {"ok":true}
  • Repeated runs (example.com, example.org) work

Out of scope / not fixed here

  • The VNC plugin (plugins/vnc/) is disabled by default in camofox.config.json. Enabling it would replace the 1x1 Xvfb with a 1920x1080 one. I did not test this path.
  • The lib/reporter.js crash/hang telemetry endpoint — unrelated, and the default config does not enable it.

Three changes that, together, get a fresh `make up` to a working browser
(out of the box, the server starts but every /tabs request fails):

1. server.js: await the async VirtualDisplay.get()
   The VirtualDisplay class from camoufox-js returns a Promise from get().
   The previous code stored the Promise object directly into vdDisplay, then
   passed it to firefox.launch() which coerced it to the string
   "[object Promise]" and set that as DISPLAY. Firefox exits with
   `Error: cannot open display: [object Promise]`. The log line
   `"xvfb virtual display started","display":{}` is the diagnostic
   giveaway: JSON.stringify on a Promise produces {}.
   Add the missing `await` so vdDisplay is the resolved "":N"" string.

2. package.json: pin playwright-core to 1.51.0
   `^1.58.0` resolves to 1.61.0, which sends `viewport.isMobile: false`
   to Firefox's Browser.setDefaultViewport CDP method. Firefox 135.0.1
   (the version bundled in CAMOUFOX_VERSION) does not describe that
   field, so newContext() throws:
   "Found property <root>.viewport.isMobile - false which is not
    described in this scheme".
   Pin to the playwright version contemporary with Firefox 135 (1.51.0).
   Note: this pin must move when CAMOUFOX_VERSION is bumped.

3. Makefile: add --shm-size=2g to the docker run
   The default Compose shm is 64 MiB, which is too small for Firefox to
   map shared memory; the browser process crashes silently after launch.
   `--shm-size=2g` matches the upstream Docker best-practice for
   Playwright + Firefox in containers.

Reproduction (before the fix):
    make down && make build && make up
    curl -s -X POST http://localhost:9377/tabs \
      -H 'content-type: application/json' \
      -d '{"userId":"u","sessionKey":"k","url":"https://example.com"}'
    # => HTTP 500
    docker logs camofox-browser | grep -E 'cannot open|isMobile|object Promise'

After the fix: HTTP 200, real tabId, GET .../snapshot returns the
accessibility tree, DELETE closes the tab.
@lindenau-cedix

Copy link
Copy Markdown
Contributor Author

Quick note for reviewers:

This PR fixes the broken make up on a fresh clone. Verified end-to-end on this branch — POST /tabs, GET /tabs/{id}/snapshot, and DELETE /tabs/{id} all return correct responses on the rebuilt image.

The diagnostic trail is in this issue I went through while finding the bugs: the symptom chain (cannot open display: [object Promise]isMobile ... not described in this scheme) is a three-bug stack that compounded. I have a reproducer + smoke-test script if useful — happy to add it to the PR or post it as a separate file.

Two questions for the maintainers:

  1. The playwright-core: 1.51.0 pin is matched to the bundled Firefox 135.0.1. If you bump CAMOUFOX_VERSION in the Dockerfile, this pin needs to move to the Playwright version released in the same window (Playwright release notes name the bundled Firefox for each version). Would you prefer a renovate.json / dependabot.yml config that moves both together, or leave the pin manual?

  2. The Makefile change is --shm-size=2g. Is there a reason the original didn't set it? If the image is meant to be used outside Docker too (e.g. directly on a Linux host via npm start), the Makefile shm flag is harmless but a note in the README would help.

Happy to address feedback or split this into three PRs (one per bug) if that's preferred for the changelog.

@MaksimKravchuk

Copy link
Copy Markdown

Verified — can confirm all three bugs on fresh make up (Linux x86_64, Docker 29.5.2)

I hit the exact same bug chain today on a fresh clone. Before finding this PR, I independently diagnosed and patched the first two issues.

Bug 1: Missing await on VirtualDisplay.get()

{"msg":"xvfb virtual display started","display":{}}          // ← Promise serialized as {}
{"msg":"camoufox launch attempt failed","error":"...Error: cannot open display: [object Promise]..."}

Root cause confirmed: camoufox-js@0.11.1 VirtualDisplay.get() is async, but server.js:955 calls it without await. The Promise gets coerced to "[object Promise]" and set as DISPLAY.

Bug 2: viewport.isMobile rejected by Firefox 135.0.1 Juggler protocol

browser.newContext: Protocol error (Browser.setDefaultViewport): ERROR: ...
Found property "<root>.viewport.isMobile" - false which is not described in this scheme

playwright-core@1.61.0 sends isMobile: false in setDefaultViewport. Firefox 135.0.1 (pinned via CAMOUFOX_VERSION) doesn't support this field → every newContext() throws → health probe fails → browser enters restart loop.

My workaround was viewport: null in all 3 newContext() call sites (health probe, session creation, Google probe). This works but is a workaround — your approach of pinning playwright-core: 1.51.0 is the correct root-cause fix.

Bug 3: /dev/shm too small

Didn't hit this one because my Docker daemon uses a larger default shm size, but the --shm-size=2g addition is correct upstream best-practice for Firefox-in-Docker.

Test results with equivalent patches

$ curl -s http://localhost:9377/health
{"ok":true,"browserConnected":true,"browserRunning":true,...}

$ curl -s -X POST http://localhost:9377/tabs \
    -H 'content-type: application/json' \
    -d '{"userId":"hermes","sessionKey":"test1","url":"https://example.com"}'
{"tabId":"b4a91934-...","url":"https://example.com/"}

$ curl -s "http://localhost:9377/tabs/b4a91934-.../snapshot?userId=hermes"
{"snapshot":"- heading \"Example Domain\" [level=1]\n- paragraph: This domain..."}

This PR is the right fix. The playwright-core pin is the correct approach — viewport: null (what I did) papers over the symptom but leaves the version mismatch for the next person. Hope this can get merged soon, it's blocking all Linux/Docker deployments.

@615Works

Copy link
Copy Markdown

Confirming this fix from the npm-deployed path (@askjo/camofox-browser, as shipped by OpenClaw). The diagnostic matches exactly: "xvfb virtual display started","display":{} in logs (JSON.stringify of a Promise is {}), and browser exits with cannot open display: [object Promise].

The await fix in change 1 resolves it. Happy to test a tagged release once merged.

@AIWintermuteAI

Copy link
Copy Markdown

Yep, unusable without these three fixes. Regretfully, there as no commits from main maintainer for 2-3 weeks...
@jo-inc hopefully will be back

@skyfallsin

Copy link
Copy Markdown
Contributor

thanks for the careful debugging here. the motivation was solid: a fresh make up reported healthy but couldn’t create a tab, and several users independently confirmed the launch failures.

the missing await and Playwright/Firefox compatibility issue have since been resolved on current master, so I’ve removed those obsolete changes. the remaining --shm-size=2g fix is still useful for preventing Firefox crashes under Docker’s small default shared-memory limit.

the PR is now a single-line Makefile change and is ready to merge.

@skyfallsin
skyfallsin merged commit f4c219e into jo-inc:master Jul 20, 2026
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.

5 participants