# Conversation 2026-07-31T17-15-52-181Z

## 2026-07-31T17:15:52.797Z · user · cli

For every ticket `tickets/<TICKET>.md` without `<TICKET>.plan.md` and `<TICKET>.lock.md`, add the following to TODO_AGENTS.md
- "Create tickets/<TICKET>.plan.md"

Put the entries in the right `## Priority` following a mix of sensible criteria after reading `tickets/<TICKET>.md` (e.g. if ticket seems low effort => higher priority).

You are one agent of a concurrent batch, so the scope above narrows: spike & plan exactly one ticket, `tickets/2026-07-28_spike-headless-extension.md`, and no other.

Your claim on it is already in place: `tickets/2026-07-28_spike-headless-extension.lock.md` holds `CLAIMED: spike-1785518149410-1`. Write the real `tickets/2026-07-28_spike-headless-extension.plan.md`, and delete `tickets/2026-07-28_spike-headless-extension.lock.md` in the same commit — the lock lifts when your work lands. If the lock file is missing, names a different agent, or a plan already exists, the ticket is not yours — stop and do nothing.

## 2026-07-31T17:36:19.844Z · agent · cli

```set-session-name
spike-headless-extension
```

```show-markdown
\# Analysis result — spike-headless-extension

- **Ambiguous prompt:** NO — spike & plan exactly one ticket (`tickets/2026-07-28_spike-headless-extension.md`); lock verified as `spike-1785518149410-1`, no plan existed.
- **Scope:** small — one `.plan.md`, lock deleted, one commit.
- **Variability:** the one high-variability problem (how to keep a claude.ai login alive in a headless profile) is a finding *inside* the spike deliverable, not a choice about how to write it — so it's presented as ranked options in the plan rather than blocking on a choice prompt.
```

```ready-for-merge
```

**Yes, the extension can run headless — I proved it by actually running it, not by reading the code.** I scripted branded Chrome 150 over a CDP pipe, loaded the unmodified `spike/cc-web-extension`, and pointed it at real claude.ai plus a stub daemon. The MV3 service worker loads, `chrome.alarms` fire on schedule (`tf-answers` every 30 s, `tf-sessions` every 60 s), the daemon receives authenticated `GET /_bridge/sessions` polls on every beat, the content script injects its panel into claude.ai, and `chrome.tabs.create({active:false, pinned:true})` works.

Three findings are worth your attention beyond the yes/no:

**Cloudflare is the real gate, and the user agent is the tell.** Plain `--headless=new` sits on "Just a moment…" forever — it never resolved in 45 s of watching. Overriding the user agent to a normal Chrome string gets straight to the login page. I isolated the cause: a *headed* browser parked offscreen also got through while still reporting `navigator.webdriver === true`, so what Cloudflare rejects is the `HeadlessChrome` token, not the automation flag. That means running headless requires telling Cloudflare the browser is something it isn't. I flagged that as a decision for you rather than folding it into the implementation steps, because #610 originally ruled out driving the claude.ai UI on Usage Policy grounds and #1328 reversed that specifically for a user-attended session.

**The hard blocker isn't technical — it's the login.** A fresh headless profile has no claude.ai session, and I can't create one (nor should I). The user's real profile can't be shared: Chrome's `SingletonLock` is live in it right now, and macOS Keychain-bound cookie encryption means copying it only works on the same machine and user. My recommendation in the plan is a one-time headed sign-in into a dedicated persistent profile that headless runs reuse afterward.

**The spike surfaced an unrelated live bug.** With a watched session whose tab lands anywhere other than `/code/session_*` — a logged-out redirect to `/login`, for instance — `openWatchedTabs` re-derives the session from the URL, fails to match, and opens another pinned tab. I measured 1 tab after one minute, 2 after two, 3 after three, unbounded, with `lastOpen` reporting `{ok:true, opened:1}` each time. This affects the headed extension today; headless just means nobody sees the pile. The fix is to attribute tabs by the id already recorded in `openedTabs` (`background.js:287`). **This deserves its own ticket** — I deliberately didn't add it to `TODO_AGENTS.md` or create a ticket file, since nine sibling agents are editing that file concurrently and my scope was this one ticket.

One encouraging detail: hidden-tab throttling doesn't hurt the current design. In a headless tab, timers run at roughly half speed (five 1 s waits took 8954 ms) and `requestAnimationFrame` never fires, but `MutationObserver` fires normally — and `content.js:652-688` already leans on the observer with the interval as a backstop only.

The plan is committed at `tickets/2026-07-28_spike-headless-extension.plan.md` (Effort 5, Uncertainty 6) with the lock released in the same commit `aad64d9f`.

Sources: [chromium-extensions PSA on `--load-extension` removal](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/1-g8EFx2BBY/m/S0ET5wPjCAAJ), [RFC on remote debugging pipes as the replacement](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/aEHdhDZ-V0E/m/UWP4-k32AgAJ), [What's happening in Chrome Extensions, June 2025](https://developer.chrome.com/blog/extension-news-june-2025)
