Skip to content

feat: CAMOFOX_USER_DATA_DIR env routes launch to persistent context#4793

Closed
DaveDev42 wants to merge 3 commits into
jo-inc:masterfrom
DaveDev42:upstream-pr/user-data-dir-env
Closed

feat: CAMOFOX_USER_DATA_DIR env routes launch to persistent context#4793
DaveDev42 wants to merge 3 commits into
jo-inc:masterfrom
DaveDev42:upstream-pr/user-data-dir-env

Conversation

@DaveDev42

Copy link
Copy Markdown

Summary

Adds support for a CAMOFOX_USER_DATA_DIR env var. When set to a path
matching /tmp/firefox-borrow-*, the wrapper switches from
firefox.launch(options) to firefox.launchPersistentContext(userDataDir, options).

When unset (default), the wrapper behaves exactly like before — same
firefox.launch(options) call, same ephemeral profile. Regression risk
on the default path: zero.

Motivation

Lets an external orchestrator clone a Firefox profile into a temp dir
and have camofox-browser launch against it, so agents can act on sites
the user is already logged into (cookies, OAuth tokens, etc.) without
the wrapper handling credentials itself. We use this from a hermes-agent
skill on top of an rsync clone of the user's daily Firefox profile.

Changes (3 commits)

  1. feat: env-var branch (62332d1)

    • New lib/launch-branch.js with a pure chooseLaunch(env) decision
      function. Validates that the env value is absolute and starts with
      the /tmp/firefox-borrow- prefix (defense in depth — the consuming
      shell also validates this, but a wrapper-side check protects
      against a maliciously edited plist).
    • server.js's launch block branches on chooseLaunch(process.env).
      The else path is the original firefox.launch(options) line
      unchanged.
    • shimNewContext(browser) helper aliases .newContext() on a
      persistent context to return the context itself (Playwright forbids
      multiple contexts on a persistent launch), with a one-shot warning
      logged. Also stubs .contexts() to return [self] so downstream
      iteration works.
    • 5 unit tests in test/launch-branch.test.js cover the decision
      function (node --test; no extra dev deps).
  2. chore: clarify shimNewContext options-ignored semantics (0892bbd)

    • Comment + warning message expanded to say explicitly that arguments
      passed to newContext({viewport, permissions, …}) are dropped in
      persistent mode. Found while reviewing the shim before merge.
  3. fix: shim browser.isConnected() in persistent mode (8ca7001)

    • BrowserContext has no isConnected(); the wrapper calls it at
      three sites (session getter, /health route, root route). Without
      the shim the first /tabs request 500s with
      browser.isConnected is not a function and /health perpetually
      reports browserConnected:false. Proxies through
      browser.browser().isConnected() when available, falls back to
      true (the context throws on use if the underlying browser is
      gone — optimistic is safe, the real failure surfaces at next op).
    • Found during end-to-end smoke testing on a real Camoufox instance.

Test plan

  • node --test test/launch-branch.test.js — 5/5 pass for the
    branch decision (env unset, empty, valid path, paths outside the
    /tmp/firefox-borrow- prefix, relative path).
  • Manual smoke against real Camoufox: confirmed
    firefox.launchPersistentContext is invoked, agent sees prior
    cookies, env-unset path produces the same launch as before, and
    /tabs//health work in both modes.

Backwards compatibility

Zero regression risk on the default (env-unset) path — the original
firefox.launch(options) line runs unchanged.

When CAMOFOX_USER_DATA_DIR is set, the wrapper enters a
single-context mode. Agents requesting an isolated newContext({…})
get the default context back with a logged warning — sufficient for
single-site agent work, not appropriate for multi-tenant scenarios.
The warning is one-shot per browser instance to avoid log spam.

DaveDev42 added 3 commits May 29, 2026 18:50
When CAMOFOX_USER_DATA_DIR is set to a /tmp/firefox-borrow-<uuid>/ path,
the wrapper switches from firefox.launch(options) to
firefox.launchPersistentContext(userDataDir, options) so the launch
reuses an existing Firefox profile (typically an rsync clone of the
user's daily profile created by an external 'firefox-borrow' shell).
Env unset → exact pre-patch behaviour, no regression risk.

Persistent launches return a BrowserContext, not a Browser. shimNewContext
aliases .newContext() to return the persistent context itself (Playwright
forbids multiple contexts on a persistent launch), and stubs .contexts()
for downstream iteration. Agent code asking for isolated contexts in
borrow mode gets the default context back with a one-shot warning.
camofox-browser's wrapper calls browser.isConnected() at three sites
(session getter ~L1086, /health route ~L2404, root route ~L5207), and
Playwright's BrowserContext has no such method. Without a shim the
first /tabs request 500s with `browser.isConnected is not a function`,
and /health perpetually reports browserConnected:false even when the
persistent context is healthy.

Proxy isConnected() through browser.browser().isConnected() — Browser-
Context.browser() returns the underlying Browser instance, which has
isConnected. Fall back to `true` if browser() is unavailable, since
context calls throw on stale browsers anyway (optimistic is safe; the
real failure surfaces at next operation).

Also bumps the shim's coverage from 2 methods (newContext, contexts)
to 3, matching the actual Browser-only API surface the wrapper relies
on. Other Browser methods (close, on, removeListener, version, etc.)
are either present on BrowserContext or not called by the wrapper.
@skyfallsin

Copy link
Copy Markdown
Contributor

Closing as superseded by the newer persistent-profile design in #6525, which is already labeled considering. We want one persistence proposal to evaluate rather than two competing launch/session models. Thank you for establishing the use case.

@skyfallsin skyfallsin closed this 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.

2 participants