Skip to content

Web runs: the Chrome extension creates the cloud session through claude.ai's repo picker (#1328) - #1693

Merged
suleimansh merged 6 commits into
mainfrom
extension-started-web-runs
Aug 24, 2026
Merged

Web runs: the Chrome extension creates the cloud session through claude.ai's repo picker (#1328)#1693
suleimansh merged 6 commits into
mainfrom
extension-started-web-runs

Conversation

@suleimansh

@suleimansh suleimansh commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

🤖 automated — the extension half of #1328, per the plan posted there. Dogfooded end to end on a real web run (see Verified). Draft only pending cleanup and a look from Rom. Refs #1328; supersedes #1330 (its daemon half is re-landed here on today's paths).

What

Daemon

  • bridge-starts.ts: the session start-queue — queued → claimed (90 s TTL) → created/failed; success without a session id is a failure; reports on unheld requests are ignored.
  • Extension face: GET /_bridge/start (claim-on-read) and POST /_bridge/started, on the bridge token + version gate.
  • Run face: POST /_web-start{id} (202), GET /_web-start/<id> → state; same token; 409 at once when no extension has called within 3 minutes, so a run without one hands off through the CLI in one round trip instead of after a timeout. Off with the bridge.
  • Every spawned run gets TF_DAEMON_URL in its environment (daemon-runtime.ts childEnv; daemon.ts hands the runtime a URL getter since the port is known only after listen).

Driver / CLI

  • CloudDriverOptions.extension ({ daemonUrl, token }), wired by cli.ts from the env var + readDaemonToken().
  • CloudSession.prompt: same anchor push, same trust write; then post the request (repo = GitHub remote slug, branch = the pushed ref, prompt = the whole hand-off prompt), poll, and report() exactly as the CLI path does. Falls back to --cloud with a notice on 409/404, no GitHub remote, or an unpushed ref. An extension that tried and failed fails the turn with its note — no silent CLI retry.

Extension 0.11.0 (daemon EXPECTED_EXTENSION_VERSION bumped in lockstep)

  • background.js: pollStarts() on the answer alarm + at boot, serial (creating guard), opens claude.ai/code pinned/inactive, waits for load, retries tf-create-session until the content script answers, reports to /_bridge/started. Created → the tab is recorded as a watched tab; failed → tab closed.
  • content.js: createSession drives the page as it actually is — combobox chips in the order repo / branch / add (the page remembers the last repo picked, so the chips are waited for and read), a picker = dialog → React-controlled search → listbox → option, only visible options count (a closed list's linger in the DOM), labels read through the page's icon-font glyphs, and the branch chip is verified to read the pushed ref before anything is sent. Session id read from the URL. probeNewSession + a diagnostic in every failure note.

Specs: new bridge-starts, web-start-endpoints (+ test specs); updated bridge-endpoints, bridge-store, server, daemon, daemon-runtime, cli, cloud (+ test specs), extension SPEC/background/content/check; FEATURES-SPEC gets the feature line.

Verified

  • typecheck clean; framework suite 1603/1603; dashboard 834/834; extension harness all cases, including seven for session creation (repo remembered / another repo remembered / none remembered / wrong branch refused / missing picker named / probe / glyphs).
  • Live, 2026-08-24 16:20Z: run 2026-08-24T16-20-39-042Z on gemstack → the extension created session_016nS9CGnCYKk5c17LCvyQZn (queue note: repo already the-framework; branch: clicked "cloud-1-baa05deb" via combobox; sent via button), the run reported the link and ended ok, the bridge mirrored the session at once, and the session committed and pushed dogfood-1693.md to claude/dogfood-1693-marker-90n7z9 — a branch descending from the pushed hand-off ref. That is the outcome --cloud could not give on a bundle-upload account (Web runs complete their work but cannot push: cloud container has no GitHub write access unless the account's Claude GitHub app covers the repo #1320).
  • Four probe runs before it, each fixed from its own failure note: chips render after the composer and the page remembers the last repo (de8e0530); the real markup (d4eff8a9); list-load wait, unfiltered fallback and a diagnostic in the note (07f00851); and the actual cause of every picker miss — icon-font glyphs inside every label in the content script's world (b29e6d9b). The branch list does offer a ref pushed seconds earlier — checked live.

Not in this PR

  • Model selection on the new-session page (the page defaults; --model is ignored on the extension path).
  • Removing --cloud — Rom's call once this has proven itself.

suleimansh and others added 6 commits August 24, 2026 18:47
…de.ai's repo picker (#1328)

A session created through the page's repository picker is repo-bound and can push and
open its pull request; `claude --cloud` has, on some accounts, produced a bundle upload that
never could (#1320). So a web run now asks its daemon for an extension-created session first.

Daemon: the session start-queue (`bridge-starts.ts`, re-landed from #1330) behind two faces on
the one token — the extension's `GET /_bridge/start` (claim-on-read) + `POST /_bridge/started`,
and the run's `POST /_web-start` + `GET /_web-start/<id>` (`web-start-endpoints.ts`), which
answers 409 at once when no extension has called within three minutes. The daemon hands every
spawned run its own URL in `TF_DAEMON_URL`.

Driver: `CloudDriver` takes `extension: { daemonUrl, token }` (wired by the CLI from the env
and the registry token); after the anchor push and trust check it posts the request — repo from
the GitHub remote, the pushed ref, the whole hand-off prompt — and polls until created, then
reports exactly as the CLI path does. 409/404, no GitHub remote or an unpushed ref fall back
to the CLI's cloud mode with a notice; an extension that tried and failed fails the turn with
its note.

Extension 0.11.0 (daemon lockstep): the worker claims requests on the answer beat, one at a
time, opens claude.ai/code pinned and inactive, and the content script drives the repo chip's
searchable list, verifies the branch chip reads the pushed ref before sending, types the prompt,
sends, and reads the session id off the address. A created session's tab is a watched tab; a
failed attempt's tab is closed and the note says what the page lacked. Harness cases cover
the drive, the wrong-branch refusal and the missing-picker report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…membered repo's chip as the picker (#1328 dogfood 1)

First live attempt: the page had remembered the last repo picked, so its chips (the-framework / main, as comboboxes)
were already there — but they render after the composer, and createSession read the page before they did, then looked
for a "Select repo" trigger that no longer exists once a repo is remembered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ips in order, visible options only, React-safe search (#1328 dogfood 2)

Run 2 clicked the repo chip itself as if it were a list entry (its text is the bare repo name) and then found no
chip "afterwards". Read from the live page: the chips are combobox buttons in the order repo, branch, add; a picker
is a dialog with a role=combobox search input and a listbox of options; a closed picker's options stay in the DOM,
so only visible options are entries, and the trigger is never one. The branch list does offer a ref pushed seconds
earlier (checked live), so verifying the chip before sending is enough.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll back to the whole list, and say what the list showed on failure (#1328 dogfood 3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yphs (#1328 dogfood 4)

In the content script's world the labels carry private-use glyphs — the chips read "\ue048the-framework" and
"\ue078main", the chosen entry "framework/the-framework\ue03b" — so no exact match ever fired. The mirror already
strips those; the control labels now do too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@suleimansh

Copy link
Copy Markdown
Contributor Author

@brillout the extension now creates the web session itself (repo picker, pushed ref) — dogfooded, session pushed. --cloud stays as the no-extension fallback for now.

@suleimansh
suleimansh merged commit abc513e into main Aug 24, 2026
3 checks passed
@suleimansh
suleimansh deleted the extension-started-web-runs branch August 24, 2026 16:25
suleimansh added a commit that referenced this pull request Aug 24, 2026
…one (#1694)

With #1693 the extension creates every web session through claude.ai's repo picker, and that
is the path that ends in a pull request; the CLI's `--cloud` flag could vanish at any release
and produced bundle uploads that never pushed on some accounts (#1320). Rom: "remove it once
the extension has proven itself" — it has.

Gone: the `script` pty runner, `CLOUD_COMMAND`/`CLOUD_ENV`, the ANSI/URL scraping, and the
whole trust write + trust-dialog detection (`claude-trust.ts`, #1493/#1314) — that existed
only because the CLI asked its folder-trust question under the daemon's pty; no CLI runs
for a web hand-off any more. The three fallbacks became named errors: a run no daemon
started ("start web runs from the dashboard"), no GitHub remote, no extension around (409,
"install or reload the extension, keep the bridge on"), bridge off (404). A failed anchor
push fails the run, since the session must open on that ref.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant