---
title: "A link check that passed on 2026-10-01 and failed on 2026-10-06"
canonical: https://dxdev.com/blog/headless-relaunch-killed-whatsapp-link/
datePublished: 2026-10-07
---
The link command printed "Linked. WhatsApp Web is connected." Then it printed "Headless did NOT come back ready, so the browser will run minimized instead." Right after, `status` read mode minimized, chrome uptime under a minute, state logged_out, daemon state qr_login, last ready 3m ago. A browser that had just reached ready was back on the logged-out screen.

## What the link command did

The sequence lives in `supervisor._finish_link`:

```python
B.write_config(backend="dedicated")
if b._config()["mode"] == "headless":
    b.launcher.kill()
    b.sleep(1)
    b.ensure_up("headless")
    if _wait_ready(b, HEADLESS_VERIFY_S):   # HEADLESS_VERIFY_S = 90
        out("Verified: the linked session also works with no window (headless)...")
        return 0
    out("Headless did NOT come back ready, so the browser will run minimized instead.")
    B.write_config(mode="minimized")
    b.launcher.kill()
    b.sleep(1)
    b.ensure_up("minimized")
b.launcher.minimize()
```

The reasoning was in the docstring. On 2026-10-01 a live launch was measured: Chrome ignores the start-minimized flag (STARTUPINFO SW_SHOWMINNOACTIVATE), opens a normal window and takes focus, with or without `--app`. Headless was the only way a restart at logon or after a crash could be quiet, so headless was the default. Whether a linked session survives headless could not be tested before the first scan. So the link command tested it on the real session: kill the browser, relaunch headless, require state ready. On 2026-10-01 that relaunch survived.

## The check that cost the scan

On 2026-10-06 the same check failed. The link succeeded in the visible window, the headless relaunch did not come back ready, and the fallback relaunched a second time, minimized. The page ended up logged out. The fallback also persisted `mode: minimized` in `browser_config.json`.

Earlier that day the profile had already dropped from ready to logged_out while running headless, with the user agent reporting HeadlessChrome. `browser.log` shows the start of that run: 09:23:22 `chrome launched mode=headless`, 09:24:22 `state=ready`. A status read that evening showed logged_out since 86m ago, chrome uptime 12h 15m, last ready 3h 55m ago.

We sent a session to drive the "Link with phone number" flow by hand over CDP in that headless browser, with the author waiting on the phone's "Enter code" screen. WhatsApp refused phone-number linking from the headless browser. That route was closed before it produced a code.

So the relaunch cost a QR scan, and getting the scan done was its own problem. The WhatsApp window sat minimized at -31993,-32000, 146x21. `desk place` was denied because another session held the desk lease. We filed a request, and the output warned that nobody was guaranteed to read it. The QR wait was due to end about 11:11pm.

## Reading the page without touching it

We asked a read-only session five questions. The first was whether the page was really on the QR screen, or still loading after the relaunch. It attached over CDP with `.scratch/wa_readonly_probe.py`, with no clicks and no reload. The probe returned:

```
"qr_canvas": true,
"qr_dataref": true,
"chat_models": 0,
"chatlist": false,
"body_head": "Download WhatsApp for Windows ... Scan to log in\nLink with phone number instead ..."
```

The session was logged out and showing the QR page. The cause was not established. The same relaunch survived on 2026-10-01 and did not on 2026-10-06.

Another of the five questions was whether the next successful link would try headless first. The persisted config said minimized, so the `mode == "headless"` branch would be skipped. The code default was still `{"backend": "9222", "mode": "headless"}`, though, and a reset or missing `browser_config.json` would put the kill and relaunch back.

## The change

```python
DEFAULT_CONFIG = {"backend": "9222", "mode": "minimized"}
```

Headless stays in `MODES` as an explicit opt-in through `the CLI whatsapp mode headless`. The commit touched 3 files, 49 insertions and 19 deletions, including `tests/test_whatsapp_browser.py`. The module docstring and the supervisor's "Why headless is the default" block were rewritten to explain why minimized is now the default.

The trade is written into the docstring: a minimized launch can flash once on a restart, and headless is the only fully silent one.

We relinked by QR and switched the browser to a minimized window by default. It stayed linked overnight. The cause of the original logout is still unknown. With `mode` defaulting to minimized, a fresh link skips the `if b._config()["mode"] == "headless"` branch, so `b.launcher.kill()` never fires on a browser that has just printed ready.
