What
On a fresh install with Browser Mode enabled (headless --config path — i.e. any user not on the Chrome-extension path), every browser_* tool call fails at launch with:
Error reading storage state from <data-home>/playwright-storage-state.json:
ENOENT: no such file or directory, open '.../playwright-storage-state.json'
The browser binary launches, but Playwright cannot build a context because the config points contextOptions.storageState at a file that does not exist.
Environment: KiroCrew 0.1.2-insider (OSS build), @playwright/mcp (headless/config mode, not --extension), macOS (Chromium engine). Chromium binary present via npx @playwright/mcp install-browser chromium.
Steps to reproduce
- Fresh install, no Chrome extension paired (
~/.kiro/crew/playwright-extension-mode absent → headless --config path).
- Enable Browser Mode (Settings → Browser). This writes
playwright-config.json.
- Ensure the Chromium binary is installed (
npx @playwright/mcp install-browser chromium).
- Trigger any
browser_* tool (e.g. browser_navigate).
Expected: the browser launches with a clean context and navigation succeeds.
Actual: launch fails with ENOENT: ... playwright-storage-state.json. The file is never created anywhere on the machine.
Root cause (verified in the installed backend)
The config generator and the only writer of the storage-state file disagree, and the writer is a no-op in the OSS build:
-
browser/setup.py:428 generate_playwright_config() unconditionally writes contextOptions.storageState (line 462) pointing at <config_dir>/playwright-storage-state.json:
"contextOptions": {
"storageState": storage_state, # setup.py:462
},
-
browser/setup.py:472 refresh_storage_state() is the only function that writes that file — and in the OSS build it bails immediately (line 481) because the SSO cookie source is stubbed out:
def refresh_storage_state() -> dict[str, Any]:
cookie_path = _cookie_path()
if not cookie_path.exists():
return {"ok": False, "error": "browser auth not available in OSS"}
...
(The module docstring confirms: "In the open-source build these steps are neutralized ... SSO setup is a no-op.")
-
Playwright's storageState requires the file to exist; pointing it at a missing path throws ENOENT at context creation. Net effect: headless Browser Mode can never launch on a stock OSS install.
Note: playwright-storage-state.json is (correctly) on the sensitive-path allowlist in security.py, since it can hold session cookies — so this cannot be worked around by an agent seeding the file; it must be fixed in the generator.
Suggested fix
Only attach storageState when the file actually exists — a fresh OSS context needs no seeded auth:
storage_state = str(config_dir() / "playwright-storage-state.json")
context_options: dict[str, Any] = {}
# The OSS build's refresh_storage_state() is a no-op, so pointing Playwright at a
# non-existent storage-state file throws ENOENT at launch. Only attach it when present.
if Path(storage_state).exists():
context_options["storageState"] = storage_state
config = {
"browser": {
"browserName": engine,
"isolated": True,
"launchOptions": launch_options,
"contextOptions": context_options,
},
"capabilities": ["network", "storage"],
}
This self-heals: if a cookie source is ever wired and refresh_storage_state() writes the file, the next config regen picks it up automatically.
Why it matters
Every fresh OSS install using headless Browser Mode (the default for users not on the Chrome-extension path) has a non-functional browser out of the box, with an error that points at a file the product never creates. Distinct from #1634 (extension-token path, --extension) and #1273 (post-launch screencast height) — this is the headless --config launch path.
What
On a fresh install with Browser Mode enabled (headless
--configpath — i.e. any user not on the Chrome-extension path), everybrowser_*tool call fails at launch with:The browser binary launches, but Playwright cannot build a context because the config points
contextOptions.storageStateat a file that does not exist.Environment: KiroCrew
0.1.2-insider(OSS build),@playwright/mcp(headless/config mode, not--extension), macOS (Chromium engine). Chromium binary present vianpx @playwright/mcp install-browser chromium.Steps to reproduce
~/.kiro/crew/playwright-extension-modeabsent → headless--configpath).playwright-config.json.npx @playwright/mcp install-browser chromium).browser_*tool (e.g.browser_navigate).Expected: the browser launches with a clean context and navigation succeeds.
Actual: launch fails with
ENOENT: ... playwright-storage-state.json. The file is never created anywhere on the machine.Root cause (verified in the installed backend)
The config generator and the only writer of the storage-state file disagree, and the writer is a no-op in the OSS build:
browser/setup.py:428generate_playwright_config()unconditionally writescontextOptions.storageState(line 462) pointing at<config_dir>/playwright-storage-state.json:browser/setup.py:472refresh_storage_state()is the only function that writes that file — and in the OSS build it bails immediately (line 481) because the SSO cookie source is stubbed out:(The module docstring confirms: "In the open-source build these steps are neutralized ... SSO setup is a no-op.")
Playwright's
storageStaterequires the file to exist; pointing it at a missing path throwsENOENTat context creation. Net effect: headless Browser Mode can never launch on a stock OSS install.Note:
playwright-storage-state.jsonis (correctly) on the sensitive-path allowlist insecurity.py, since it can hold session cookies — so this cannot be worked around by an agent seeding the file; it must be fixed in the generator.Suggested fix
Only attach
storageStatewhen the file actually exists — a fresh OSS context needs no seeded auth:This self-heals: if a cookie source is ever wired and
refresh_storage_state()writes the file, the next config regen picks it up automatically.Why it matters
Every fresh OSS install using headless Browser Mode (the default for users not on the Chrome-extension path) has a non-functional browser out of the box, with an error that points at a file the product never creates. Distinct from #1634 (extension-token path,
--extension) and #1273 (post-launch screencast height) — this is the headless--configlaunch path.