A Windows MCP server that gives an AI agent a real browser through Playwright's own MCP server, keeps each session in a directory that outlives the agent and can be resumed or handed to another, and ships as one installer that updates itself.
BrowserAI is a proxy. It ships the runtime, owns the lifecycle, and rewrites the tool surface. It does not reimplement Playwright, and it does not reimplement Playwright's MCP tool layer. That boundary is the single most important design constraint in this project and is spelled out in Scope below.
Windows only, and there is nothing to install alongside it. No Node, no .NET and no Chrome on the host: the installer carries a NativeAOT single-file binary, node.exe and the vendored @playwright/mcp tree, and provisions its own Chromium on first run. One MCP registration per client, at user scope, available in every repository -- no .mcp.json, no hooks, no per-repository files, unless you register it in one project. Corrected 2026-10-12 (previously "One MCP registration, at user scope, available in every repository -- no .mcp.json, no hooks, no per-repository files"): the installer registers with Claude Code and with Codex, and the section on registering in one project describes a project's .mcp.json.
Why it exists, and every settled decision with the argument that settled it, is in DECISIONS.md.
- Download
BrowserAI.exefrom the latest release and run it -- it is the installer. (NamedBrowserAI-win-Setup.exeuntil 2026-09-15.) It installs per user into%LocalAppData%\BrowserAI.appand needs no elevation. Your data -- the browsers it downloads, the index of your session directories and its log -- goes in%LocalAppData%\BrowserAIbeside it, and stays there across an update, a reinstall and an uninstall. Uninstalling asks before deleting it; a silent uninstall keeps it. - That is the whole installation. The installer's own hook registers BrowserAI with Claude Code and with Codex, each through the client's own supported command --
claude mcp add --scope userandcodex mcp add-- so it is available in every repository on the machine. It runs those commands through RegisterAI, a small program of the same author's that the installer carries, which reads each entry back after writing it. (RegisterAI arrives in the first release after 1.1.0.) A client that is not installed is skipped and named in the registration record below, and the install still succeeds. The uninstaller removes both registrations again. (Codex registration arrives in the first release after 1.1.0; 1.1.0 registers Claude Code only.) - BrowserAI's page opens in your own browser when the install finishes, and the BrowserAI entry in your Start Menu opens it again in a new tab whenever you want it. It shows the installed version and where BrowserAI is installed, where its data and its logs are, whether a downloaded update is waiting, and how BrowserAI is registered with each client. A second page lists the BrowserAI running in the background, its sessions and the clients connected to it; it closes nothing. A downloaded update has a page of its own, which shows what holds it, each with its countdown, and has the button that installs it now. For each client separately you can register or unregister for all your projects, register in a project you choose, or remove it from a project; a project folder is chosen in Windows' own folder picker, which may open behind your browser. When a registration is already correct the register button reads Register again, which writes it back the way BrowserAI would, and that is the way back after editing your own copy. It changes nothing unless you click something. The page lives at an address on your own machine that BrowserAI makes for each run and nobody else is given, and it asks to close itself when you open a newer one. Changed 2026-10-03 (previously "A small BrowserAI window opens when the install finishes, and there is a BrowserAI entry in your Start Menu that opens it again whenever you want it"), because the page replaces the window; and again later the same day (previously "Registration still lives in a small BrowserAI window, which the page's registration button opens"), when registration moved into the page and the window was deleted. Changed 2026-10-10 by addition (previously "a second page listing every BrowserAI server running from this install with the sessions it holds, from which you can close the ones you pick. An update a server has already downloaded is offered there with a button that installs it"): since 2026-10-08 the second page lists the one BrowserAI running in the background, with every session it holds, and each client connected to it, and it closes nothing, because a client's BrowserAI ends with the client and the background is ended from its own task or when you sign out. A downloaded update has a page of its own, which shows what holds it, each with a countdown, and installs it from its install button, which reads "Install BrowserAI now". Corrected 2026-10-10, round 2 of the texts review (previously "installs it from its "Install now" button"): "Install now" is the heading over the button and the name of the toast's button. Corrected in the sentence itself later the same day, after the texts review (previously "where its data and its logs are, an update check, how BrowserAI is registered with each client, and a second page listing every BrowserAI server running from this install with the sessions it holds, from which you can close the ones you pick. An update a server has already downloaded is offered there with a button that installs it"): the background checks for updates by itself, so the page offers no check, and since the maintainer's 17 a the second page offers no close. Changed 2026-10-11 by addition, the maintainer's "40-46 your recommendations": the page opens on a home that says first whatever needs you and then what runs now, each conversation in Claude Code or Codex with the browsers it opened, and its cards for the update, for Claude Code and Codex, for the session folders and for this install each lead to a page of its own with a way back. The conversations page draws every conversation, BrowserAI and every browser session as a map joined by lines, or as a list in a narrow window or on a crowded day, and it and the home follow every change as it happens. Changed 2026-10-13 by addition: the map is drawn however many conversations and sessions there are, and the page grows longer for them and never wider; only a narrow window shows the list. On the page an app is set up where it was registered: the buttons read Set up for all my Claude Code projects, Set up again for all my Claude Code projects, Repair the Claude Code setup, Remove BrowserAI from Claude Code, Set up in a project for Claude Code and Remove from a project for Claude Code, and the same for Codex. The page deletes nothing: the storage page shows each session folder's size with a button that opens it.
- Restart the client so it picks up the new server. Claude Code reads its MCP configuration when a session starts, so sessions you already have open will not see BrowserAI until they are restarted. Codex does not pick the change up in a thread that is already open; start a new thread.
If registration did not happen -- a client was not found, or it is neither Claude Code nor Codex -- BrowserAI writes mcp-registration.json into its data root, %LocalAppData%\BrowserAI unless the install was made with another (which since 2026-10-10 only the suite's test install can be), with one entry per client carrying the exact command to run by hand. For an install made with no data root of its own they are these:
claude mcp add browserai --scope user -- "<install root>\current\BrowserAI.exe" --mcp
codex mcp add browserai -- "<install root>\current\BrowserAI.exe" --mcp
and for an install made with one, each line ends --mcp --data-root "<data root>" instead, because the background serves the data root the install was made with and a client has to start BrowserAI for that one. Corrected 2026-10-10, round 2 of the texts review (previously "into %LocalAppData%\BrowserAI, with one entry per client carrying the exact command to run by hand. They are these:", with the two lines ending in --mcp alone): since the same day each line carries the arguments the registration itself was given; the record already went where the install keeps its data, and the sentence had named the default folder. Corrected again the same day, the texts polish (previously "and the record goes where the install keeps its data"), because the note dated the record's place to that day as well.
Changed 2026-10-08 (previously both lines named <install root>\current\BrowserAI.Server.exe
and passed no argument): BrowserAI is one program again, and --mcp is what tells it that a
client started it.
Codex is looked for in four places, because its desktop app puts the command line on no search path at all: PATH, ~\.local\bin, the desktop app's own manifest under %LocalAppData%\OpenAI\Codex, and %AppData%\npm.
current\BrowserAI.exe. It is what Claude Code
and Codex start, with --mcp, and what opens the page described in step 3 when you start
it yourself, with no argument. Changed 2026-10-08 (previously the paragraph below, written
2026-09-15): the two programs are one file again. A registration that names
BrowserAI.Server.exe is repaired by the next update, which registers the new command;
a project file that names it has to be registered again from the page, because nothing
rewrites a file in your repositories.
The paragraph as it stood from 2026-09-15 until 2026-10-08, set off as a quotation on 2026-10-10:
⚠️ The file name changed on 2026-09-15 (previouslycurrent\BrowserAI.exe). BrowserAI ships as two programs now, in one installer:BrowserAI.Server.exeis the MCP server, which is what Claude Code starts and what the command above names, andBrowserAI.exeis what opens the page described in step 3. If you have an older registration it still names the old path; updating repairs it, and so does the page's Repair the Claude Code setup button (Repair the Claude Code registration until 2026-10-11). An update leaves an entry that already names this install's server as it is, arguments you added to it included; Set up again for all my Claude Code projects on the page (Register again until 2026-10-11) is what writes it back the way BrowserAI would.
Registration is never allowed to fail an install, and never allowed to fail silently: every outcome writes a log record and that file.
If BrowserAI is gone after an update -- %LocalAppData%\BrowserAI.app\current is
missing and no client can start BrowserAI -- run the installer again: download
BrowserAI.exe from the latest release
and run it, with --silent if you want no window. It puts the program back. Your data is
kept, because it lives in %LocalAppData%\BrowserAI, outside the install folder; anything
else you put inside %LocalAppData%\BrowserAI.app is deleted by the repair. Until you have
run it, an uninstall fails as well. Update.exe --uninstall --silent failed in 4 tries of 4,
and Windows Settings runs the same command without --silent, which was read in Velopack's
source and not run. Corrected 2026-10-12 (previously "the one Windows Settings runs is the
same command"), as the kb has it. The cause is
Velopack 1.2.161, which BrowserAI updates with. When another program, such as antivirus or
backup software, keeps a file of the new version open for more than about 30 seconds while
the update swaps the versions, Velopack deletes the old version and starts nothing. That was
measured on 2026-10-10 and reported upstream as
velopack/velopack#1086, with a fix in
#1087; the hazard row
has the rest.
The page's Set up in a project for Claude Code (Register in a project for Claude Code until 2026-10-11) asks for a folder in Windows'
folder picker and writes a .mcp.json at its root (the window's Register in a
project... until 2026-10-03, when the page took registration over) -- the file Claude Code reads for project-scoped servers.
It is meant to be committed: a teammate who clones the repository is then
offered BrowserAI without configuring anything, and Claude Code will ask each
of them to approve the server once, the first time they open a session there.
The command written into it is the bare
BrowserAI.exe with the argument --mcp, which Claude Code finds on the PATH it
was started with, where the installer puts BrowserAI's own current folder, so it is
right on every teammate's machine and not just on the one that wrote it. A Claude Code
that was already running when BrowserAI was installed does not find it in a project
until it is restarted. Corrected 2026-10-10, the maintainer's 30 (previously "portable
-- ${LOCALAPPDATA}/BrowserAI.app/current/BrowserAI.exe with the argument --mcp,
which Claude Code expands on each machine --"), measured that day with Claude Code
2.1.296 (kb). (Until 2026-10-08 it named
BrowserAI.Server.exe, which no build ships any more; a project file that still
names it needs registering again.) (Corrected 2026-10-10 (previously "If you installed BrowserAI somewhere other than
the default location, the entry gets that absolute path instead and the page tells you
why."): a shipping install is only ever at the default location since that day.)
For Codex, the same button writes .codex\config.toml in the folder you choose, by
running codex mcp add with CODEX_HOME pointed at that .codex folder, so the
file is Codex's own. BrowserAI.exe with --mcp and no folder (BrowserAI.Server.exe
until 2026-10-08), because
Codex expands no variable in a server's command -- measured: none of four spellings of
LOCALAPPDATA started anything in 48 attempts -- so the portable spelling above does
not work there. Codex finds the name on your PATH instead, and the installer puts
BrowserAI's own current folder on your user PATH and the uninstaller takes it off
again, so the same committed file serves every machine with BrowserAI installed.
Removing a project registration is on the page too: each client offers
Remove from a project, which asks for the project folder and removes BrowserAI's
entry from that folder's own .mcp.json or .codex\config.toml. When BrowserAI was
started from a terminal inside a repository, each client with a registration of ours
at or above that folder also offers Remove BrowserAI from that project, naming the
file. Started from the Start Menu, BrowserAI starts in its own install folder and finds
none, so only the first button appears. (The window's links until 2026-10-03.) Neither removes another install's entry: one
found in the folder you pick is reported and left alone.
When a registration or a removal in a project cannot be done, the page says what
happened and gives what fixes that project's own file: the entry to put into it, written
the way the registration would have written it, or that BrowserAI's browserai entry is
to be deleted from it. Changed 2026-10-10 (previously a registration in a project that
was not done offered the line that registers BrowserAI for all your projects, with
${LOCALAPPDATA} left for your shell to expand, and a removal the line that adds it).
This is an addition, not a replacement: registering for all your projects is still one entry in your own configuration with no file in any repository, and that is still what the installer does for you.
A redirected user profile is not supported, and BrowserAI refuses it by design instead of half-working on it. If %LocalAppData% resolves onto a UNC path or a mapped network drive -- a roaming or folder-redirected profile -- startup refuses the app root and says so by name; and a session directory on a network path is refused at the door for the same reason, in every spelling, including a drive letter that only resolves to a share once the filesystem is asked. The refusal is deliberate and not a restriction that can be lifted by asking: the session record is a SQLite database in WAL mode, whose shared-memory index has only ever been established on a local volume, and a refusal that names the path is a better answer than storage whose guarantees nobody has measured where you put it.
Updates are automatic, and there is no beta channel. The BrowserAI running in the background checks its own feed, at most once every ten minutes, and downloads an update when there is one. It installs it once nothing uses BrowserAI: every session's countdown and every connected client's has run out, and every client has said it may end. Until then a toast counts down to the install and the update page has the button that installs it now. A build that was not installed by the installer never checks. Corrected 2026-10-10 after the texts review (previously "BrowserAI checks its own feed and applies an update only when no other instance is live. The check happens once, when the server starts, and on no other schedule -- there is no polling timer, and opening a session or a hundred browsers inside one does not ask again, because they are all children of the one server your client started."): since 2026-10-08 one BrowserAI runs in the background and the update is its own.
The first session downloads a browser. Chromium is provisioned once per machine, not once per update -- measured at 208.8 MB down, 440.61 MiB on disk and 13.25 s and 17.05 s on two runs (chromium 1247, re-measured 2026-10-03; previously 207.3 MB, 437.24 MiB and about 10.8 s at chromium 1244, and 203.8 MB, 430.48 MiB and about 12.6 s at chromium 1237 before that). Nothing is downloaded at spawn after that, and nothing resolves from a registry at runtime: the client runs exactly the bytes the build froze into the artifact.
BrowserAI presents upstream's browser_* tools under upstream's own names, byte-for-byte, plus nine tools of its own (corrected 2026-10-10, previously "eight": browserai_close arrived 2026-10-08). A session is a directory, and that is the only handle there is: every upstream tool has a required session parameter injected into its schema, carrying the absolute path of the session the call belongs to. A call that names no session is refused instead of reaching a browser.
Nine authored tools, all prefixed browserai_ (corrected 2026-10-10, previously "Eight", counted in SessionToolSurface.Names):
| Tool | What it does |
|---|---|
browserai_init |
Creates a session. directory and purpose are required and have no default, and an empty, relative or unusable path is refused. Added 2026-10-10 by addition: since 2026-10-08, F2 d, headed, transcript, captureNetwork and idleMinutes are required as well, here and on browserai_resume, because a person notices each; a resume that changes one is held back once and then switches the browser itself |
browserai_resume |
Takes over a directory that is already a session, with its recorded browser, purpose and history. browser is not an argument; it was bound at init and is read back out of the session's own record. It is also how a moved session comes back: there is no move tool and no copy tool, so the directory is moved by hand while no browser is open on it and resumed at its new path |
browserai_close |
Closes the session's browser cleanly and keeps the session: its profile with the logins and cookies, its site storage, its tabs with their history, and its directory and log. The browser is given up to a minute to write what it holds to disk, and every later browser call that names the session is refused, saying who closed it and why, until browserai_resume opens it again; browserai_catch_up, browserai_change_purpose, browserai_destroy and browserai_close itself still answer it. Playwright's own browser_close is withheld in its favour. Added 2026-10-08, F1 a. Corrected 2026-10-10, round 2 of the texts review (previously "every later call that names the session is refused"), as the tool's own description says it |
browserai_list |
Every session beneath a directory you name -- browser, purpose, created and last-used stamps, size on disk, and whether it is in use right now: YES when this BrowserAI is driving it or something else holds its lock, no when nothing held it and that directory's own gate was free, UNKNOWN with the reason when either could not be settled. Corrected 2026-08-24 (previously "no when nothing held it at the instant of the look") -- a busy session's record is briefly present and unheld while it is being rewritten, and the look landed in that window and printed no about a session another agent was driving. The holder is deliberately not named -- the lock says the file is held, not by whom. There is no unscoped form: breadth is stated and not assumed |
browserai_destroy |
Closes the browser and deletes the whole directory -- everything in it, screenshots and downloads included, so move out what must be kept first. Refuses anything that does not hold a valid session record, which is what stops it being aimed at Documents. Windows will not unlink a file a browser is still mapping, so a destroy that could not remove everything reports an error -- and that error names every survivor, says the session itself is gone, and says not to call this tool again on that directory because there is no longer a session there to destroy |
browserai_catch_up |
Answers what were we doing here, and what is here now for one session, from two sources that routinely disagree: the session's own ordered log -- every call, with what the caller said each was for -- and a walk of the directory: age, last touched, total size, a breakdown by artifact kind, whether the profile holds a cookie store, and any HTTP Archive it finds. It takes no lock it can be refused by, so it answers for a session another BrowserAI is driving right now. It takes a why, required since 2026-10-04, which it writes to the session's log after its answer when this BrowserAI holds the session. Corrected 2026-10-10, round 2 of the texts review (previously "it is the one session-scoped tool with no why, because a tool that told you what happened by adding to what happened would bury its own answer"), as the tool's own description says it Corrected 2026-10-10 again (previously "every browser call and every purpose change" and "Read-only and takes no lock it can be refused by"): the log holds every call, browserai_catch_up's own among them, and it writes its own why there. |
browserai_change_purpose |
Rewrites what a session is for. The previous purpose is kept in the session's history and not lost. Renamed 2026-10-04 (previously browserai_set_purpose), with no alias: the old name is answered as a tool BrowserAI does not have |
browserai_page_tool |
Calls a tool the page offers, if it offers any -- see Tools a page offers. name is the page's own name for it, arguments is an object that reaches the page's code unchanged, and the optional page refuses the call if the tab has navigated since you read the tool. Added 2026-09-21. |
browserai_reinstall_browser |
Deletes and re-provisions one browser tree. browser is required and has no default -- with two families on disk, a defaulted one would re-download a healthy tree and report success while the broken one stayed broken. It refuses -- naming them -- while any session of that family is open, whether or not a browser is currently running out of the tree. Changed 2026-08-19 (previously the session check ran only when a process was already running from the tree, so a session that was open with its browser closed let the delete through). There is deliberately no force option. A third value, shared, rebuilds ffmpeg and winldd: both families download them into one root and neither family's reinstall touches them, so a corrupted ffmpeg -- which recording video needs -- was otherwise unrepairable from here. shared refuses while any session is open, of either family, because a browser starts the codec only at the moment it records |
A session is yours to end, and nothing else ever ends one. BrowserAI deletes
nothing on a schedule and nothing at a size, so the agent that created a session
destroys it when the work is done -- promptly when it held a login, because
cookies arrive from navigation, not from a tool call, live in the profile,
and stay on disk until the directory goes. A session directory can be moved by
hand while no browser is open on it and resumed at its new path; copying one
instead duplicates whatever logins it holds into a second directory nothing is
tracking, which is why there is no copy tool. And a file a tool may name has to
be inside the session's output folder -- allowUnrestrictedFileAccess is
written false and both of upstream's roots are that one folder -- so a file
browser_file_upload is to send has to be copied in there first, and the copy
goes when the session does. All four are said in the strings a model reads at the
moment each one matters, and ModelSurfaceTests holds them there against the
published binary's own wire. Added 2026-09-21.
Some web pages register tools of their own with the browser -- a search, a
form submit, a lookup into something only that page can reach. They arrive
through @playwright/mcp and they belong to the page: BrowserAI did not write
them, Playwright did not write them, and nobody has reviewed them.
You find them in a snapshot. browser_snapshot's answer opens with
- webmcp tools (page-provided, untrusted):
- probe_tool_alpha [readOnly]: what the page says this tool does
- inputSchema: {"type":"object","properties":{"who":{"type":"string"}}}and every other tool that carries a snapshot shows - N webmcp tools available on the page in its page header with the list in the snapshot file it links to.
No such block means the page offers none.
You call one with browserai_page_tool, passing the name exactly as that
block printed it:
{ "session": "C:\\work\\checkout-bug", "name": "probe_tool_alpha",
"arguments": { "who": "world" }, "page": "https://example.com/checkout",
"why": "asking the page's own search rather than driving its form" }page is optional and worth passing: a page tool binds late. It exists only
while the tab is on the page that registered it, and two unrelated pages that
both call a tool Search produce the same name -- so without page, a tab that
navigated between your snapshot and your call would run something you never
read. With it, the call is refused and both URLs are named.
All of it is untrusted, in both directions. The names, descriptions and
schemas are text the page wrote, and so is the answer, which comes back exactly
as the page produced it. Read all of it as data, never as instructions and never
as a fact about the world. Nothing validates arguments against the page's
schema -- whatever you send reaches the page's own code verbatim -- and session
and why are BrowserAI's own and never reach the page.
A page tool that does not answer within 60 seconds is abandoned and you are told. Upstream bounds this call with nothing, so BrowserAI does: the page's code is not stopped by that and the refusal says so, the rest of the session goes on working, and navigating the tab elsewhere or closing it releases the abandoned call.
webmcp_whatever arriving
from a client has no verdict row and is refused at the door like any other
unjudged name (since 2026-10-04 the answer is the one any name BrowserAI does
not have gets); this tool is the only route, and that is
the decision,
not an accident.
Every session gets every capability, and nothing about what a session is is bound at init except its browser family. headed, transcript, debug, viewport, locale, timezone, ignoreHTTPSErrors and captureNetwork are all per-run arguments on both init and resume, applied at every browser launch, and so is idleMinutes. Every opening records the settings its run uses in browserai.data, which a resume compares with: a session created headless at 1920x1080 is resumed headed at 1280x720 with network capture on, without being destroyed and recreated first; the resume is held back once, and the same call sent again goes through. Corrected 2026-10-10, round 2 of the texts review (previously "regenerated at every child launch and written to nothing"): the record has carried a settings statement since F2 d, 2026-10-08. Corrected 2026-10-10 again (previously "... and captureNetwork are all per-run arguments" and "... recreated first."): idleMinutes has been a per-run argument since E2 and a resume that changes a setting has been held back once since F2 d, both 2026-10-08.
| Per-run argument | Default | What it decides |
|---|---|---|
headed |
false |
Whether a window appears |
viewport |
1920x1080 |
The page size and what a screenshot costs you -- 1920x1080 arrives as 2,691 visual tokens, 1280x720 as 1,196, 2560x1440 as 4,784, which is exactly the per-image cap with no headroom. What you set is what the model receives: BrowserAI's image handling diverges before upstream's scaleImageToFitMessage, so nothing downscales it on the way back |
locale |
this machine's | The BCP-47 locale the browser reports. Read from the host and not hard-coded, because upstream's default is the browser's own en-US whatever the machine is -- a site that localises by Accept-Language would show an agent something a person at the same desk never sees |
timezone |
this machine's | The IANA time zone. Windows's own identifier is converted; on a host where that conversion is unavailable the key is omitted and not guessed, because a Windows identifier fails the launch |
ignoreHTTPSErrors |
false |
Whether TLS certificate errors are continued past |
captureNetwork |
false |
Whether this run writes an HTTP Archive. Three things before you turn it on: it changes what the site does, because service workers are blocked while it is on; it takes effect at the next browser launch and is never retroactive; and the file is a plaintext credential dump. Each launch gets its own timestamped filename in the session's output\ directory, so resuming a session cannot overwrite the previous run's capture |
transcript |
false |
Whether upstream writes session.md, a Markdown transcript of the run: every browser tool call that succeeded, with its arguments and its result, page snapshots and screenshots included, in a new output\session-<milliseconds> folder per run. Text typed into a page, passwords included, is in it as plain text, and a call that failed is not in it (measured). Renamed and corrected 2026-10-04, Q371 c (previously tracing, "Whether upstream writes session.md, a Markdown log of the run's tool calls with their arguments and what each returned, into a folder of the session's output\ directory. It is not a Playwright trace, and the trace viewer cannot open it: browser_start_tracing and browser_stop_tracing record one."). A call that still sends tracing is refused as an argument the tool does not have. |
debug |
false |
Whether this session's own log level is raised |
headed, transcript and
captureNetwork have no default: they are required on browserai_init and on
browserai_resume, with idleMinutes, how long the session's browser may go
unused before BrowserAI closes it, a whole number of minutes or "never"; the tool's
own description gives 10 minutes without a window and 60 with one as its defaults. A
call that names the session starts that countdown again, and so does a person's input
in a visible window. A longer time keeps BrowserAI's automatic updates waiting while
the browser is open, unless the person chooses Install now, which closes it. A call
that sets one is held back once and says so, unless it is a resume that repeats its
last run's time and mode. Corrected 2026-10-10, round 2 of the texts review
(previously "A longer time keeps every update waiting while the browser is open, so a
call that sets one is held back once and says so."), as the argument's own
description says it.
serviceWorkers: "block" is not optional beside recordHar. A request served out of a worker's cache never reaches the network layer the archive is written from, so without the block the capture is silently incomplete -- and incomplete in the direction that matters, because the requests a worker serves are the repeat ones a reader is looking for.
Four things are hard-coded and are not arguments. The console level is debug always -- measured, error→debug costs +1 character on a navigation response and +5 otherwise, because the events line is a pointer (path#L1-L20), not the message text, and browser_console_messages already takes its own read level, which can be lowered at the moment of asking where a capture level chosen at init cannot be raised retroactively. (consoleLevel was an argument until 2026-08-20 and is deleted.) codegen: "none", which strips a ### Ran Playwright code block from every response for a feature this product does not have. snapshot.boxes: false, which is upstream's own default: a model that needs coordinates for a browser_mouse_*_xy tool passes boxes: true to browser_snapshot on that one call, and the server instructions tell it to. snapshot.boxes: true, whose cost is deferred -- a response carries a link, not the snapshot -- and which the vision capability's six coordinate tools are unusable without"). The cost was deferred for an action's response and not for browser_snapshot, which returns its snapshot inline: boxes on cost 175,611 tokens against 105,804 off over nine pages, measured 2026-09-25 at @playwright/mcp 0.0.82 and Chrome for Testing 154.0.8037.0, counted with o200k_base as a proxy tokenizer; and vision grants six tools, of which three take coordinates. And permissions: ["clipboard-read"] -- clipboard-write is already granted without asking -- initializeServer with Unknown permission: clipboard-read and the browser exits, so writing it for both families makes every Firefox session unusable, not degraded.
init and recorded in the session's browserai.json. A mode is two switches on the browser BrowserAI launches for that session -- whether a window appears, and whether the cookie and storage tools exist in that session's child" -- a table of headless / interactive / persistent against Window and Cookie-and-storage-tools reading No·No, Yes·No, Yes·Yes; then "A session without them has its child launched without upstream's storage capability, so the 17 cookie, localStorage and storageState tools do not exist in that process at all"; then a 2026-08-19 correction recording that all three modes persisted on disk and that storage was a tool filter, not a persistence switch; then "tracing is a boolean on any of the three, not a mode of its own. Headless-with-storage is deliberately not offered.")
The modes are gone. What they decided was a window and a capability set. The window is now an argument, and the capability set is everything -- the reason is the one the 2026-08-19 correction above had already established and the 2026-08-18 one before it: it was never a boundary against the caller, who owns the session directory, reads the profile inside it as the same Windows user, and could reach whatever a missing capability withheld by resuming the same directory as a different mode. What it cost was real: a headless session that needed to read one cookie had to be destroyed and recreated.
Ten tools became reachable in that change -- for the first time in this product's history and in the predecessor's. Upstream's network capability (browser_route, browser_route_list, browser_unroute, browser_network_state_set), its pdf capability (browser_pdf_save) and its testing capability (browser_generate_locator and four browser_verify_*) had never been named in a generated config at all. browser_run_code_unsafe is not among them and never was -- it is core, so it has been in every session this product has ever opened.
browser_route can make a page lie to a human watching a headed window. A mocked response renders as if it came from the server: the address bar keeps the real origin, and nothing on screen says a rule is in force. That warning is in the server instructions, which BrowserAI writes, not appended to the tool's description, which passes through byte for byte. browser_route, and of browser_network_state_set for going offline, since the maintainer had the instructions rewritten down to the rules that span tools. Upstream's text still arrives unchanged in front of it.
Every call that names a session also carries a required why. It rides the same path session does -- injected into the JsonNode the child sent, appended to properties and to required, with everything upstream wrote left where it was -- and it is stripped again before the call is forwarded, so the child never sees it. It is on every upstream browser tool and on browserai_resume, browserai_destroy, browserai_change_purpose and, since 2026-10-04, browserai_catch_up; not on browserai_list, which is directory-scoped, nor on browserai_reinstall_browser, which is machine-scoped, because neither has a session record to write into; and not on browserai_init, which asks for a purpose instead -- two mandatory free-text fields on one call gets one thoughtful answer and one restatement.
It asks for why, not what, and the descriptions do that work instead of leaving it to a model: "checking whether the login survived the redirect" beats "clicking the submit button", because the tool name already said the second one. A call that omits it is refused before anything is forwarded, with a sentence that says what to write, not only that something is missing -- a model told "'why' is required" retries with a restatement, which satisfies the schema and records nothing.
Where it goes is one time-ordered log inside browserai.data. init's purpose, every purpose change, every browserai_change_purpose, every browserai_catch_up, every browser call the session forwarded and every call it refused are rows in the same ordered table, so a reader sees the human changed the purpose here sitting between the calls it explains. A row is written in-flight immediately before a call is forwarded -- so a call that never returns still left one -- and settled to successful or failed with the time it settled, from which the duration comes for free. A call whose row cannot be written is refused, not forwarded.
browserai.json", and "a call BrowserAI refused leaves none, because this records what the session did and the refusals are in browserai.log beside it"). The record is a SQLite store now and the session's own log file is gone -- everything it carried is on stderr. So a refusal is a row: the record is the only place the agent reached for a tool this build will not forward survives at all.
purpose and why are different things and the schemas say so. A purpose is the session's standing description -- what browserai_list shows six weeks later. A why is disposable: why you are doing this right now, one entry in the log, shown in no listing. browserai_change_purpose takes both, and its descriptions carry the same example on both sides: "the original login bug turned out to be a redirect loop" is a why; "tracking the checkout redirect loop on staging" is a purpose. If what you are writing would still be true next week, it belongs in purpose.
browserai_catch_up is what reads it back, a page at a time -- oldest first, so a page number keeps meaning the same entries as the log grows -- and it holds the log against the directory instead of believing either alone. The log says what BrowserAI did; the directory says what is true now, and the two routinely disagree in one direction that matters: cookies arrive from navigation, not from tools, so a session whose log shows no cookie call at all can hold a live signed-in profile. It reports age, when it was last touched, total size and a breakdown by artifact kind, names the profile's cookie store when there is one, and calls out any .har by name -- a HAR records every request and response including headers, so every bearer token and session cookie that crossed the wire is in it in clear text. Since 2026-10-04 (Q365.4) it also names a saved login browser_storage_state wrote, a Playwright trace's folder and a transcript's session.md, the other three kinds of file that hold login data in clear text (measured). And since the same day (4 a) it names every file that can hold something sensitive, one line per kind with what it holds: the profile, page snapshots, logs, what the network tools saved, screenshots, PDFs, videos, files saved by name, downloads in flight and its own record as well.
value and text are never stored, at any length, as <withheld, N characters>; an object or an array becomes a shape, <object, N keys>; and everything else is cut at 200 characters with a count of what was dropped, which is what turns a browser_evaluate body into a summary rather than a transcript." A row carries the tool's name, verbatim, and your why. Nothing sits between your call and the browser server, and an argument summary is BrowserAI reading your request and writing its own account of it -- the why answers the same question better, and it needs no list of upstream parameter names to stay honest. What is lost is worth naming: from the log alone you can no longer see which selector was typed into, or that a password field was filled at all.
init, recorded in the session's browserai.json, and enforced server-side on every call"). There was a (tool, mode) permission policy behind that phrase -- five tool classes, deny-by-default in both directions, plus a guard that refused a browser_get_config answer carrying "secrets" -- and it has been removed. It was described as a security boundary and was never one against the caller: the calling agent chooses the session directory, the profile and its cookie database are created inside it, and the agent runs as the same Windows user, so DPAPI decrypts for it. Any file tool the agent holds reads what the policy declined to return. Prompt injection is real and is not solved at this layer. That sentence was measured on 2026-08-18, after the removal, not before it -- a second process as the same user recovered a cookie from a session BrowserAI configured with CryptUnprotectData and AES-GCM alone, and App-Bound Encryption is not in force for the provisioned Chromium (kb).
Change control moved to the release gate, which covers more: four golden snapshots -- including tools-list.json with every tool's inputSchema -- are diffed against the resolved payload on every build, and upstream-review.json holds a release until a human adjudicates what moved.
Nine tools are withheld and two arguments are mandatory on every upstream tool; none of that is a permission. session is mandatory, because that is routing, and why, because it is what the session's log records. Corrected 2026-10-10, round 2 of the texts review (previously "One tool is withheld and one argument is mandatory; neither is a permission. session is mandatory, because that is routing."): the correction below already counted nine, and why has been required beside session since 2026-08-20. And browser_annotate is not in tools/list at all -- filtering the surface is in scope where renaming is not -- because it blocks with no self-timeout until a human draws, and its window belongs to a second, non-headless browser under a daemon that writes into %TEMP% and outlives the session. A caller that names it anyway is refused, not forwarded. Corrected 2026-10-10 by addition: nine tools are withheld since 2026-10-08, every deny row of tool-verdicts.json: browser_annotate since 2026-08-18; browser_resume, browser_generate_locator and the four browser_verify_* since 2026-10-03; browser_set_storage_state since 2026-10-04; and browser_close since 2026-10-08, in favour of browserai_close. A call that names one is answered like a tool BrowserAI does not have. Corrected 2026-08-18 (previously "Two refusals survive ... browser_annotate is refused on a mode that opens no window"). The measurement is in kb; what it would take to bring the tool back is in DECISIONS.
output\ is flat: a file lands where the browser server put it, under the name your call gave, and the answer that names it comes back byte for byte as the browser server wrote it. What that removes is a whole class of wrong answer -- a pointer naming a file that had been moved out from under it, a console log recreated at the old path and landing as -2, an answer citing lines a 24-line file did not have. What it costs is stated and not hidden: a name you reuse overwrites, and a name Windows silently rewrites -- NUL.png, a trailing space -- is no longer refused. Both are open rows in HAZARDS.
TODO.md names the day the check goes (kb).
The session directory is the identity. One directory holds browserai.lock and browserai.data at its root and profile/, output/ and downloads/ beneath it -- corrected 2026-08-26 (previously "holds browserai.json at its root"). browserai.lock says who owns this directory, and its open handle is the whole of that: it is written once and released by Windows however the holder dies. browserai.data says what happened here, and it is the file a copied session carries with it. There is no handle to keep, no token to store and no expiry: a session stays resumable against its recorded directory for as long as the directory exists, and a resume costs about 515 ms and loses only sessionStorage. Everything a tool writes lands in output/ under the name the call gave it. Corrected 2026-08-26 (previously "Artifacts are routed into typed subfolders on the way in, and every result carries the resolved absolute path").
Every session holds the machine's browsers root shared for its whole life, and a reinstall needs it to itself. Added 2026-08-19; reader/writer since 2026-08-20. Two agents on one machine could otherwise race: the reinstall establishes that nothing is running out of the tree, and the other one's init launches a browser into that tree while the delete is part way through it -- the check was right when it was asked. browserai_init and browserai_resume take the claim shared, any number at a time; browserai_reinstall_browser takes it exclusively, which Windows refuses while any session holds it. It knows nothing about browser families: a live Firefox session refuses a Chromium reinstall, because nothing may run out of the root while it is being replaced. A process that dies releases its claim, because the kernel closes the handle however the process ends. There is no drain, no wait and no intent marker. A reinstall that finds sessions open refuses at once and names them; it never holds the machine waiting for a browser a human may never close, and it does not stop new sessions starting meanwhile. Anything refused by a reinstall is told how far in that reinstall is -- what has been staged, how long it has been running, and the rate those two give.
%LocalAppData%\BrowserAI.app and nowhere else, and keeps its data in %LocalAppData%\BrowserAI. Added 2026-10-10, the maintainer's decision of that day, verbatim: "21 refusing installing into a non-standard folder so the project specific setups always resolve on every dev's pc." Velopack's installer still accepts --installto, so a copy installed elsewhere is set up with nothing: no client registration, no scheduled task, no PATH entry and no toast activator. It says why in a box after a non-silent install and at every start, "Uninstall this copy, then run BrowserAI.exe again without --installto.", and uninstalling it touches neither the data root nor any client. The shipping install reads no BROWSERAI_ROOT; the suite's test install still does (kb).
BrowserAI refuses to start when either of its roots is not inside your own Windows profile. Added 2026-08-20; narrowed and then widened again 2026-09-15. %LocalAppData% gives every user their own browsers directory, session index and log, and their own install directory; BROWSERAI_ROOT can move the data root and the installer's --installto, BrowserAI.exe --installto as the release ships it, can move the install root, and both are now judged. Corrected 2026-10-10, round 2 of the texts review (previously "Setup.exe --installto"): the release renames Velopack's Setup.exe to BrowserAI.exe, and its parser reads --installto from its arguments and not from its name (kb). (Previously "its app root ... BROWSERAI_ROOT and the installer's install-to flag both defeat that", then "its data root ... what --installto still moves -- the install root, and with it the live-instance markers keyed to it -- is not judged here". The first was written when there was one root; the second was true for a few hours, and the gap it named was closed the same day.) Two users sharing one root is unsafe in a way nothing reports at run time -- the background's pipe is named for its roots and open only to the user who made it, so the second user's background finds the name taken and exits, leaving that user's clients with no background; and applying an update terminates every process under the install root, the other user's browsers included (kb). Corrected 2026-10-10 (previously "the file locks span users, but the machine-wide mutexes do not, so the second user's process cannot join the live set, creates no marker, and is invisible to the first user's census; that census then answers nothing else is running, and"): no process joins a live set since the one background of 2026-10-08, and the census was deleted under the maintainer's 9 a; the refusal's own sentence says the same since that day. The refusal is a log line, a non-zero exit and, from a background's start, a record of its own, which every relay reads to answer each call with which root was refused, why and how to put it right. It names both roots -- because two different levers move them and neither can move the other's -- then offers the one that can move whichever is at fault: for the data root, install again with BROWSERAI_ROOT cleared or naming a directory under your profile, since no running BrowserAI reads the variable; for the install root, install inside your profile. Corrected 2026-10-10, round 2 of the texts review (previously "The refusal is a log line and a non-zero exit, and it names ... clear BROWSERAI_ROOT for the data root, install inside your profile for the install root."): the record is the maintainer's 9 a of the same day, and the variable is read by the install's hooks alone since step 5 of the one-binary build. The check resolves through the filesystem instead of comparing strings, so a junction, a substed drive letter, an 8.3 short component or an extended-length spelling of a per-user root is served, and a junction inside your profile that points outside it is not.
Two browser families, chromium and firefox. The family is the one thing init binds permanently: a profile belongs to the browser that made it, so resume reads the family back out of the record and refuses to be told a different one. chromium is the default, because it is the most widely used browser -- the maintainer's reason, recorded in DECISIONS.md, and a decision, not a measurement. Each is downloaded once per machine on first use -- chromium 207.3 MB, firefox 129.5 MB, both measured from the CDN's own content-length (kb); corrected 2026-09-17 (previously "chromium 203.8 MB, firefox 127.2 MB"), re-measured 2026-09-16 at chromium 1244 and firefox 1544 -- and the first browser call on a family that is still downloading is refused with a progress report and not blocked -- bytes written so far against that measured total, elapsed, the observed rate, and what the remaining bytes come to at that rate. Changed 2026-08-19 (previously the size and "wait about ten seconds", which read the same at 8 s in and at 25 minutes in, so nothing told a caller whether the download was working). Upstream emits no progress notifications at all, so this refusal is the whole mechanism.
A download is stopped when it stops making progress, not when it has taken too long. Changed 2026-08-19 (previously a 45-minute ceiling on the whole install). A total-time cap can only ever fire on a link that is slow and working -- 207.3 MB in 45 minutes is 0.61 Mbps (corrected 2026-09-17, previously "203.8 MB in 45 minutes is 0.60 Mbps"; the same arithmetic over the 2026-09-16 figure) -- while a link that has died is caught by Playwright's own 30-second socket timeout twenty times sooner. What is watched now is bytes on disk under the browsers root, and the cap is ten minutes with nothing written at all: that is set by upstream's own installer lock, which legitimately writes nothing for up to 470 s while it queues behind another install. Corrected 2026-08-19 (previously "Chromium only, today. browserai_init refuses browser: "firefox""). What that sentence was waiting for was a measured Firefox download size and a decision about what browserai_reinstall_browser reinstalls when there are two trees; both are done.
The advertised tool surface does not depend on which family a session runs. Measured 2026-08-19 against real children of the resolved payload at both capability sets: 42 tools without storage and 59 with it, identical names, identical order, identical schemas under chromium and firefox (kb). Every tool-surface number in this repository is therefore a claim about both.
Any path is accepted, deliberately. BrowserAI does not constrain a session directory to a sanctioned root, so an agent may point one at a real browser profile and read live browser state. Correct use is the calling agent's responsibility -- BrowserAI logs the resolved absolute paths instead of enforcing a boundary. The reasoning is in DECISIONS.md.
BrowserAI spawns @playwright/mcp as a child process and forwards JSON-RPC to it.
This is a hard boundary. It exists because the temptation to cross it is real and will present itself as a reasonable next step.
- Spawning and supervising a pinned
@playwright/mcpchild over stdio - Forwarding
tools/callto that child and returning its response verbatim - Fetching
tools/listfrom the child at runtime and rewriting it -- filtering, re-describing, adding parameters. Not renaming: upstream names pass through byte-for-byte (Tool naming) - Generating the child's
--configJSON at launch time from BrowserAI's own session state - Everything around the protocol: locking, lifecycle, directories, artifacts, diagnostics, updates
- Driving Playwright directly (via
Microsoft.Playwright/ Playwright for .NET or any other binding) - Hand-writing tool schemas in C#. Every schema must originate from the child's
tools/listresponse. If a tool definition is being typed into a.csfile, the boundary has been crossed. - Reimplementing the snapshot/ref system, the accessibility-tree serialization, response formatting, or error shaping
@playwright/mcp 0.0.79 is a 20-line shim. The entire package is cli.js, index.js, and type definitions:
// node_modules/@playwright/mcp/index.js
const { tools } = require('playwright-core/lib/coreBundle');
module.exports = { createConnection: tools.createConnection };The implementation lives in playwright-core/lib/coreBundle.js -- 3,536,648 bytes, esbuild-bundled, containing an 83-entry tool array. The numbers matter, because a golden test written against the wrong one fails on day one: 83 is the internal registry; 74 is the maximum ever exposed over MCP (9 are skillOnly and always stripped), and 27 is the default with no capabilities set -- all three in kb: tool surface, which also carries the per-capability breakdown, counted. Corrected 2026-09-17 @ playwright-core 1.64.0-alpha-2026-09-17 (previously "3,503,383 bytes ... an 82-entry tool array ... 82 is the internal registry; 73 is the maximum ever exposed over MCP ... and 26 is the default"). Re-measured off the resolved payload, not incremented: the dated override added one core tool, browser_emulate_media, and nothing else moved. core again, so the default moved again -- three bumps running now, and the only one that left the default alone was the 0.0.80 pair, which landed in devtools. Corrected 2026-09-15 @ playwright-core 1.64.0-alpha-2026-09-14 (previously "3,469,825 bytes ... an 80-entry tool array ... 80 is the internal registry; 71 is the maximum ever exposed over MCP ... and 24 is the default"). Re-measured off the resolved payload, not incremented: the roll inside @playwright/mcp 0.0.81 added two core tools and nothing else moved. The default moved this time and did not last time -- core is unconditional, so a tool arriving there is in the default surface, where the 0.0.80 pair landed in devtools and was not. Corrected 2026-09-14 @ playwright-core 1.63.0-alpha-2026-08-31 (previously "3.4 MB ... a 78-entry tool array ... 78 is the internal registry; 69 is the maximum ever exposed over MCP ... which also records that the per-capability breakdown was never counted"). Re-measured off the resolved payload, not incremented: the roll inside @playwright/mcp 0.0.80 added two devtools tools and nothing else moved. The last clause was separately stale -- that breakdown was counted on 2026-08-16 and the section it links to has said so since. Its value is not browser control; Playwright for .NET does browser control perfectly well. Its value is the ref-based accessibility snapshot system, the response formatting, and the error handling -- the layer that turns a browser into something a language model can operate. That layer is large, subtle, actively developed upstream, and would drift permanently the day it is forked.
"We don't want to reimplement Playwright" is the easy half of this rule. The half that matters: reimplementing the MCP tool layer is also reimplementation, even though it never touches a browser API.
playwright-core explicitly whitelists "./lib/coreBundle" in its exports map, so require('playwright-core/lib/coreBundle') is a supported import, not a blocked deep path. It exposes browserTools (a flat array of plain, inert objects), filteredTools, createConnection, and BrowserBackend. defineTool is literally the identity function -- there is no class, no registry, no side effect (kb: tool surface).
This means in-process tool manipulation is available if the proxy approach ever proves insufficient. It is not the plan, it carries no type definitions and no semver guarantee, and taking it requires pinning @playwright/mcp and playwright-core together and re-verifying the tool array on every bump. Documented here so it is a considered decision, not a discovery.
| File | Holds | Changes when |
|---|---|---|
README.md (this file) |
What BrowserAI is: what it does, how to install it, how to use it, the scope boundary, the licence | The product's surface moves |
DECISIONS.md |
What we decided, and why -- the founding argument, the trade-offs taken, every settled decision with the reasoning attached | We change our minds |
ARCHITECTURE.md |
How the product is put together: each area, what it guarantees, and the code that implements it | The code moves |
HAZARDS.md |
Every known failure mode, in one checkable list, with the evidence that closed each one. Maintained by addition; rows are never deleted | A new failure mode is found, or an open one is closed |
RELEASING.md |
The checklist a release must pass, and the release gate it enforces. The only gate that exists | An item proves unevidenceable, or the gate moves into automation |
TESTING.md |
Why the suite is the only thing between a floating dependency and a shipped regression: the five layers, what the build itself must fail on, why the upstream-review gate is the suite and not a hook, and why the harness is ours | The suite's shape changes, or a gate moves |
STACK.md |
Which component was chosen and why, the MSVC prerequisite, where the version comes from, the build configuration, and the nine SDK deviations | A component is replaced, or a deviation stops being needed |
kb/ |
What we measured -- about Chromium, Firefox, Playwright, Node and Windows, one article per topic, with provenance and a re-verification hook | Upstream ships, and a re-measurement says something different |
TODO.md |
Work settled in intent but not yet done | Something gets decided, or gets done |
UPSTREAM-REVIEW.md |
The procedure for adopting a new upstream version | The procedure proves insufficient |
Different half-lives, which is the whole reason for the split. A decision stays true until we revisit it. An architecture stays true until the code moves. A measurement stays true until upstream ships, which is a clock nobody here controls. Mixing them means the whole document reads as equally settled, and the parts with the shortest half-life are exactly the ones that quietly stop being true. When a document here states a measured fact, it is a summary -- the article under kb/ carries the number, the date, the versions it held under, and how to re-establish it.
There was also an implementation plan, one file per section under
plan/, plus aTODO.mdfull of closed items and a log of the first release run. All three were consumed: a plan section was spent the day the code it described existed, and the whole set was deleted on 2026-08-17 once every section was built and audited. What survived it is the documents above that were never part of it, plus everything the sections had put intokb/and into the doc comments as they went.git logis the record of the build itself.
Tagged v2.0.0 and published. No install is known beyond the maintainer's own.
v1.1.0 and published.") --
v2.0.0 has been the standing release since 2026-10-13T05:37:44Z, gh release list
reads it Latest, and the second sentence is unchanged. The paragraph below is about
v1.1.0 and is left as written.
v1.0.0 and published.") --
v1.1.0 has been the standing release since 2026-09-23T11:50:58Z, gh release list
reads it Latest, and until this correction the string 1.1.0 appeared nowhere in
this file. The second sentence is unchanged and is the half that still matters: a
download is not an install, and no session in the wild has ever been observed. The
paragraph below is about v1.0.0 and is left as written, because it is the account of
a sentence that was wrong, not a description of what is standing now.
v1.0.0. Nothing has been
distributed.", itself corrected 2026-08-24 from "Shipped. v1.0.0.") --
that sentence was false, and it was false in the direction that creates
obligations, not the direction that relaxes them. A non-draft release has
stood at releases/tag/v1.0.0
since 2026-08-17T01:54Z carrying an installer, a full package and the update
feed, all publicly downloadable; GitHub's counters read 1 download of the
installer, 0 of the package and 687 of releases.win.json, which is an
installed BrowserAI polling for updates, not a person. The 2026-08-24
correction was derived from git tag --list and a gitignored Releases/, which
are the two places that cannot see a GitHub release -- so a claim was re-checked,
re-stamped and left wrong. The distinction that was load-bearing survives,
narrowed: design arguments reasoned from "v1.0.0 shipped, so sessions exist
in the wild" are still reasoning past the evidence, because a download is not
an install and no session in the wild has ever been observed. The 2026-09-15
cut replaces that release object instead of adding to it; which one is standing
is what gh release view v1.0.0 says. Nothing enforces this sentence -- the tag is what
git tag --list says, the release is what gh release view v1.0.0 says, and the
installed base is still what a person knows.
1485 executed test cases -- measured from the two-shell gate of 2026-10-14 at 75ef1e5d, after the maintainer's 171: the background's record awaited beside its pipe, the stop arm's hang detector and its kept rig, the payload arm's signed-in read, the Codex arm's budget and the Firefox front-door arm's screenshot asked again, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 11m 56.3s, and one from Git Bash forcing c:\ at 11m 50.9s, both 1485 of 1485 with 0 skipped, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.89, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both, with the machine's commit charge at 39 to 42 % of its limit. In the Git Bash half the Firefox front-door arm's first screenshot was answered with upstream's TimeoutError after 5,024 ms and asked for again, and was whole in 1,248 ms, the red of 7a069ecb met again and taken as 171 decided (previously "1483" from the two-shell gate of 2026-10-14 at d0094014, after the hand-moved clock's three orders and the idle countdown arms' wait, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 11m 16.0s, and one from Git Bash forcing c:\ at 11m 58.0s, both 1483 of 1483 with 0 skipped, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.81, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both, with the machine's commit charge at 38 to 41 % of its limit (previously "1480" from the two-shell gate of 2026-10-14 at 7a069ecb, after the restart registration reader's answer for an exited process nothing holds, the registry reap arm's hold of the sweep gate, the dashboard arm's own folder, the .NET 10.0.13 move and the relay rig's clock, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from Git Bash forcing c:\ at 11m 46.4s, 1480 of 1480 with 0 skipped, FULL RUN, publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.75, every capability PRESENT, no test on a degraded path, its windows and firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side, with the machine's commit charge at 38 to 42 % of its limit; and one from PowerShell forcing C:\ at 11m 18.4s at the same commit, 1479 of 1480, its one red the Firefox front-door arm's screenshot answered with upstream's TimeoutError after 5.3 s, which is in that arm's row of the hazard index, with the same rows otherwise. The first PowerShell half, at 86f9dd8a, is not the cited run: it was 1478 of 1480, the registry reap arm waiting out its hang detector for a record a skipped sweep never writes, which cb6b9173 fixed, and the Codex arm's registration running past its budget, each in a row of the hazard index (previously "1477" from the two-shell gate of 2026-10-14 at f6c43676, after the maintainer's 169 c, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 12m 46.1s, and one from Git Bash forcing c:\ at 12m 34.6s, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.55, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both, with the machine's commit charge at 54 to 62 % of its limit (previously "1476" from the two-shell gate of 2026-10-14 at 9c3466a5, after the release stamp's line feeds and the correction of row 85 of the re-verification index, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 11m 52.2s, and one from Git Bash forcing c:\ at 12m 24.7s, each the second half from its shell at that commit, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.52, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both. The first half from each shell at 9c3466a5 is not the cited run: each was 1475 of 1476, the PowerShell one with a Chromium process of the containment arm that could not be opened to read its restart registration, and the Git Bash one with 40 of the reap arm's planted descriptors gone before its destroy, each in a row of the hazard index and each arm green in three runs alone straight after, with the machine's commit charge at 82 to 86 % of its limit in every half (previously "1475" from the two-shell gate of 2026-10-13 at df12ab85, after the maintainer's request that the conversations page draw its map at every count and his 164 c to 167 a, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 11m 40.9s, that half run a third time at the same commit, and one from Git Bash forcing c:\ at 11m 59.9s, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.47, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both. Two earlier PowerShell halves at df12ab85 are not the cited run: the first, whose test pack refused a publish built as 2.0.1-alpha.0.46, one commit before that tree, ran no test, and the second, 1474 of 1475, had a Firefox session's screenshot answered with isError, which is in a row of the hazard index, with the machine's commit charge at 84 to 86 % of its limit in every half, "1473" from the two-shell gate of 2026-10-13 at c72ca5ae, after the maintainer's 161 to 163, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 12m 04.7s, and one from Git Bash forcing c:\ at 11m 43.1s, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.42, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both (previously "1459" from the two-shell gate of 2026-10-13 at 9d469b94, after the maintainer's 154 c to 160 c, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 12m 13.7s, and one from Git Bash forcing c:\ at 12m 01.1s, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.30, both with every capability PRESENT, no test on a degraded path, both windows rows and both firefox safe mode rows CLEAN, and the clearance's tool-list reading none either side of both (previously "1453" from the two-shell gate of 2026-10-13 at f95e6425, after the maintainer's 127 c, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 11m 37.7s, and one from Git Bash forcing c:\ at 11m 31.3s, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.24, both with every capability PRESENT, no test on a degraded path, and both windows rows and both firefox safe mode rows CLEAN. A PowerShell half started before the branch was rebased onto 56b9b74b is not the cited run: its test pack refused a publish built as 2.0.1-alpha.0.12, two commits before that tree, and no test ran, "1440" from the two-shell gate of 2026-10-13 at afe6aab1, after the maintainer's 103 e and 147 b, beside the installed BrowserAI 2.0.0, whose background was checked before and after each half and whose own session answered a call before the gate and after each half: one full dotnet test run from PowerShell forcing C:\ at 11m 29.9s, and one from Git Bash forcing c:\ at 11m 31.8s, both FULL RUN, both publish-FRESH with the binary and the tree both at 2.0.1-alpha.0.14, both with every capability PRESENT, no test on a degraded path, and both windows rows and both firefox safe mode rows CLEAN. A first PowerShell half at the same commit is not the cited run: its test pack refused a publish built as 2.0.1-alpha.0.13, one commit before the tree, and no test ran, "1438" from the release gate of 2.0.0 on 2026-10-13 at 17482f7f, beside the installed BrowserAI, whose background was checked before and after each run: three full dotnet test runs from PowerShell forcing C:\ and three from Git Bash forcing c:\, from 04:03:51Z to 05:24:04Z, all six FULL RUN under BROWSERAI_RELEASE_RUN and each 1438 of 1438. Five earlier attempts of 2026-10-12 and 2026-10-13 are not the cited run, and each of their reds is a row of the hazard index: the walk limit's arm at 435856ef; the page listener's refusal and the screenshot limit's crash at 44368995; the tab close and the slow install at bed09d9e; and the registration's click at 25508550; the fifth was stopped after its first run for the maintainer's 142 c, "1360" from the two-shell gate of 2026-10-12 at 16426ee4, after the maintainer's 78 to 82, three earlier PowerShell halves of that day not being the cited run, "1346" from the two-shell gate of 2026-10-11 at 473af502, after the maintainer's decisions 54 and 63 and his 64 c, with the eleven waits kept for his review suppressed and uncommitted, four earlier gates of that day not being the cited run, "1329" from the two-shell gate of 2026-10-11 at 7368de5a, after 40 to 46 and 47 b, two earlier PowerShell halves at d30de345 not being the cited run, "1304" from the two-shell gate of 2026-10-11 at ce1d9869, after 34 d and 37, "1302" from the two-shell gate of 2026-10-10 at 07ea5d60, whose halves were started at below-normal priority, after two gates at that commit that are not the cited run, one whose Git Bash halves read 1301 of 1302, their one red the background refusal arm of the hazard row added that day, and one whose halves skipped the ten real-installer arms over a test install a stopped run had left, "1294" from the two-shell gate of 2026-10-10 at d853a341, "1284" from the two-shell gate of 2026-10-10 at 87bc65e0, "1275" from the two-shell gate of 2026-10-10 at 0ec1131d, "1190" from the two-shell gate of 2026-10-09 at e78c1b53, "1004" from the two-shell gate of 2026-10-04 at ec7b841, "982" from the two-shell gate of 2026-10-04 at 3feadc1, whose first PowerShell half read 981 of 982, its one red the coordinator wake arm of hazard row 364, and whose cited run was that half run again at the same commit, "965" from the two-shell gate of 2026-10-04 at 45b060d, "952" from the two-shell gate of 2026-10-04 at 89e6607, "951" from the two-shell gate of 2026-10-04 at eaaa278, "959" from the two-shell gate of 2026-10-03 at a32bf62, "929" from the two-shell gate of 2026-10-03 at 2630d89, "912" from the two-shell gate of 2026-10-03 at 6e6388d, "911" from the two-shell gate of 2026-10-03 at 6a3fb08, "943" from the two-shell gate of 2026-10-03 at dffe9d4, "929" from the two-shell gate of 2026-10-03 at 1a60597, "923" from the two-shell gate of 2026-10-03 at 9a1b921, "904" from the two-shell gate of 2026-10-03 at ea235f6, "870" from the two-shell gate of 2026-09-24 at fcdb1c1, "853" from the two-shell gate earlier the same day, "841" from the two-shell gate earlier the same day, "831" from the two-shell gate earlier the same day, "817" from the two-shell gate earlier the same day, "814" from the two-shell gate earlier the same day, "807" from the two-shell gate earlier the same day, "788" from the two-shell gate of 2026-09-23, "787" earlier the same day, "784" from the six-run release gate of 2026-09-22, "783" from the two-shell gate earlier the same day, "781" from the first two-shell gate of 2026-09-22, "779" from the two-shell gate of 2026-09-21, "766", "770" and "766" earlier the same day, "765" on 2026-09-18 and "764" earlier that day, "762" from the six-run release gate of 2026-09-17, "761" from the two-shell gate earlier the same day, "755", "750" and "747" earlier the same day, "741" from the six-run release gate of 2026-09-16, "737", "720", "706", "685", "676" and "675" earlier the same day, "671" earlier still, "652", "651", "650", "647", "644", "643", "644", "641", "640", "637", "626", "624", "613", "604", "634", "648", "625", "622", "618", "614", "603", "601", "596", "593", "589", "585", "582", "576", "573", "571", "551", "548", "532", "531", "530", "514", "505", "506", "500", "501", "498", "497", "495", "493", "491", "478", "476", "461", "458", "436" and "419" before that; re-measured each time and not adjusted))). The +1 is the packer's own asset list, later on 2026-09-23. One arm in ReleaseScriptTests drives build/Set-UploadAssets.ps1 over the list vpk pack really writes and holds that the portable archive is gone from it, that keeping it is what leaves it there, and that a list with no full package is refused. Planted red twice: with the filter removed the step refused itself and the arm named nothing, so the by-name assertion was moved ahead of the set comparison and the second red read "Expected to not contain" the archive by name. The +3 is the upload set and the evidence index, 2026-09-23. Two arms in ReleaseScriptTests hold what a release publishes -- that build/New-Release.ps1 declares the installer, the full package and releases.<channel>.json and nothing else, and that every other file a pack leaves behind is classified, not merely absent from the set. Both were planted red: a doctored declaration that published the portable archive again came back naming it, and dropping RELEASES from the classified list came back naming that against the real directory. One arm in HouseRuleTests holds that every batch under docs/evidence/ is a row in that directory's own index, in both directions -- planted red, and it named three batches that were already missing, the oldest of them two days old. The +13 is browserai_page_tool, 2026-09-21. Ten arms in PageToolTests drive it through the published binary against real pages that really register WebMCP tools -- a happy path whose page echoes back exactly the arguments the caller sent and nothing else, a name the page does not offer, a tab that navigated away, the same name on two pages with page given, two tools with one name on one page, a display title that is not the name, a page tool that never answers, and the door still refusing a page-supplied name outright. Three arms in ErrorCatalogueTests provoke the six new refusal rows. Watched red twice, and the second one is the interesting one. With the routing disabled and everything else in place, 8 of the 10 arms went red on their own first assertion, each reading "'browserai_page_tool' is not a BrowserAI session tool"; the two that stayed green are the two that are about existing behaviour, which is the control. Then, with the argument object taken from the caller's whole node instead of its arguments member, exactly 1 of the 10 went red -- and what it printed is the defect itself: the page's own handler echoed back {"session":"C:\Source\...\page-tool-session","name":"Do The Thing!","arguments":{"who":"world"},"why":"the suite exercising this call"}. The -4 is four tests RETIRED with the mechanism they guarded, 2026-09-21, and it is the first time this number has gone down. PayloadTests.TheDatedPlaywrightCoreOverrideIsStillNeeded, .TheExpiryComparisonFiresInBothDirections, .TheResolvedPlaywrightCoreIsWhateverTheOverrideSays and SessionPolicyTests.TheWebMcpCallIsWithheldOnLivenessAndTheWebMcpListIsNot were deleted, not skipped. The first three answered has the dated playwright-core override's exit fired; @playwright/mcp 0.0.82 shipped the fix the override was taken for, the overrides block went, and a mechanism that no longer exists cannot be tested. The fourth asserted that the WebMCP pair was judged in two directions, and upstream took both tools off the wire, so every premise it rested on is gone. Deleting a test for a mechanism that no longer exists is not a skip, and each deletion is named where the test was referenced and not removed without trace -- here, in TODO.md, in DECISIONS.md and in the source files themselves, which carry a RETIRED comment in the place the code stood. What the first three guarded came back to where it was before 2026-09-17: PayloadTests.TheLockRecordsUpstreamsOwnExactPinOfPlaywrightCore asserts the resolved playwright-core equals the declared one again, watched red against a doctored lock, and that is also what catches an override being added back.
The +2 of the maintainer's 171. KeptOnRedTests.AFolderKeptWithNoFailureIsMovedWholeOutOfTheScratchAndTheAnswerSaysWhere, watched red on 2026-10-14 in a filtered run against a KeptOnRed.KeepAsync for an arm that has not failed which answered its sentence and moved nothing, the folder still in the scratch at the arm's first assertion; and FirefoxSessionTests.AFirefoxScreenshotThatOutlastsUpstreamsActionTimeoutIsAnsweredWithItAndTheNextIsWhole, red in the same run with its browser let go before the screenshot was asked for, answered whole after 0.1 s, and green with it held, answered with upstream's TimeoutError after 5,026 ms and the next screenshot whole. The five arms 171 changed were each watched red in the same runs against a plant never committed, which their hazard rows record.
The +3 of the hand-moved clock's three orders. ManualClockTests.ATimerDisarmedAfterTheClockChoseItEndsTheStepWithoutAnError, .ATimerArmedFromAReadingAStepHadLeftFiresLateAndTheClockNeverMovesBack and .ATimerReArmedAfterTheClockChoseItRunsTheChosenTickAndFiresAgainAtItsNewDue, each watched red on 2026-10-14 in a filtered run of the class against the clock as it stood at 10895f93, with the seam that forces each order added: the first step of the disarm arm ended with InvalidOperationException, "Nullable object must have a value.", out of ManualClock.FireTheNextDueBy; the timer armed during the step read the clock at tick 5000000 after the first had read it at tick 10000000; and the re-armed timer fired once, at tick 20000000, with the chosen tick at one second never run. Green with the clock keeping the due it chose a timer by, moving only forward and keeping a re-arm made after its choice, in a filtered run beside IdleCountdownTests, RelayTests and BackgroundUpdatesTests, 92 of 92. Four arms of IdleCountdownTests changed and add nothing to the count: before each step to an exact deadline they wait until no call holds the countdown, which could not be planted red, as its row in the hazard index says.
The +3 of the batch of 2026-10-14, with the relay rig's clock. RestartRegistrationTests.AProcessThatHasExitedAndIsHeldByNothingReadsAsGoneAndOneThatRefusesIsStillRefused, watched red on 2026-10-14 in a filtered run against the reader as it stood at 4b3d339c, "Process 97460 could not be opened to read its restart registration (Win32 87)", and green with the reader answering Gone for 87; its other half, a probe of its own whose access control list it empties, threw on Win32 5 in every run. ProcessLogTests.TheReadAPollTakesLeavesOutOnlyTheFilesNamedForADayBeforeTheWritersCreation, red the same day with the filter's comparison planted the other way round, "Expected to not contain "written the day before"", and green as written. And RelayTests.AHeldCallIsAnsweredAtItsDeadlineWhenAStepMeetsTheRelayArmingItsHoldTimer of b427ae6d, watched red at that commit in a filtered run of RelayTests, the engine holding the hold timer due at tick 2999999999 with the clock at tick 1500000000, and green with the rig moving its clock only while the relay is settled, 88a92b67, whose own gate stopped, so this gate is the first full run of it. Two arms changed and add nothing to the count. ServerRegistryReapTests.ADestroyReapsTheDeadDescriptorsSparesALiveOneAndDoesNotWaitForEither was red with its plant moved ahead of the slice's start, "Expected to be 1000 but found 0", and is green holding the sweep gate, with the slice's pass made to find the gate taken as well. And DashboardTests.NoPageCallsAnAppAClientOrShowsAPidOutsideItsDetailsForTroubleshooting was red with the rig's scratch folder named page-rig-relay-<guid>, and is green under the same plant with the rig's folder taken out of the page text.
The +1 of the maintainer's 169 c. SessionHostTests.AResumeAfterOneRefusedForAHeldProfileStillKnowsWhichConnectionCreatedTheSession, watched red on 2026-10-14 in a filtered run against the tree at df445d33, before the change went in: with the session's Firefox profile held open by a probe process the way Firefox holds parent.lock, the resume after the refused one told the connection that had made the session "you are driving a session this connection did not create", and its open logged created by this connection: False, as the background's log of the sighting in the rig of 125 b did. Green with the connection keeping which sessions it created, in the same filtered run as the session classes it touches, 147 of 147, where a resume from a connection that did not create the session is still told.
The +1 of the stamp's line feeds. ChangelogTests.TheStampWritesLineFeedsAndNoCarriageReturn, watched red on 2026-10-14 at cdd781bb in a filtered run, against build/Get-ReleaseNotes.ps1 as it stood at df445d33: the stamp of a scratch fixture whose every line end is a line feed left two carriage returns in it, and the stamp arm beside it, which reads through ReplaceLineEndings, stayed green. Green with the stamp writing two line feeds.
The +2 of the map at any number of conversations. PageBrowserTests.InARealBrowserAMapOfManyRowsGrowsInHeightOnlyAndKeepsBrowserAIAndTheInspectorInView's two arms, Chromium and Firefox, and DashboardTests.TheMapIsDrawnWhateverTheNumberOfConversationsAndSessions in the place of .TheMapIsDrawnUpToItsLimitAndAListBeyondIt, each watched red on 2026-10-13 in a filtered run before the change went in. With the page's two files as they stood at e09dfd79 all three were red: the raw-HTTP arm on the rows' markup at three rows, and the browser arm in both engines with no map drawn at forty rows, "rows=0.". Each of three plants in the change as built turned the browser arm red in both engines. With the rule that takes BrowserAI's card to the top of a tall column taken out, the card was out of view with the page at its last row. With the arrow keys' row taken out, a step right out of BrowserAI's column landed four rows above the conversation it came from, c-1013-0 for c-1017-0, once the arm's step started from a conversation at the bottom of the window; from the map's last conversation, as the arm first stepped, that plant had stayed green, so the arm's step was moved before anything was pushed. And with BrowserAI's column no card of its own, a click on the column chose nothing, which turned the raw-HTTP arm red as well, on the column's markup.
The +14 of the maintainer's 161. FirefoxRestoreTests' fourteen, each watched red on 2026-10-13 in a filtered run before the change went in. With SessionManager.cs as it stood at f509229d, alone and with the other three files 161 changed, the six arms that resume a session were red: .AResumedFirefoxSessionStartsItsBrowserClosesTheBlankPageAndSaysWhatCameBack, .AFirefoxResumeWithNothingToReopenReadsTheTabsOnceAndClosesNothing, .AFirefoxResumeWhoseBrowserDoesNotStartSaysWhatTheBrowserServerAnswered, .ACallMadeWhileTheResumeStartsTheBrowserWaitsForTheStart, .AFirefoxResumeWhoseTabsNeverComeUpSaysSoAfterTheWait and .AResumedFirefoxSessionWhosePageCannotBeReachedStartsAndSaysSo, the one in a real Firefox. With five strings planted in FirefoxRestore, the four arms of the start's own reading were red: .TheTabListIsReadTheWayUpstreamWritesIt, .TheBlankPageGoesOnlyWhenItIsFirstAndAReopenedTabIsListed, .TheToolsTheStartCallsAreUpstreamsOwnAndAllowed and .TheDocumentReadIsMatchedToTheTabListAndOnlyTheErrorPageMarksATab. With the wait for the start taken out of the forwarded call and of the agent's close, and the texts of that day's reading not yet in, .TheAgentsCloseMadeWhileTheResumeStartsTheBrowserWaitsForTheStart, .AFirefoxResumeWhoseBlankPageIsNotListedFirstClosesNothingAndSaysSo and .OnlyTheErrorPageOfAnAddressThatGaveNoAnswerMarksATab were red, with the call's arm and the failed start's arm on its new sentence. With the wait for the restore still counted from the start of the browser, .AFirefoxResumeWhoseBrowserStartsSlowlyStillWaitsTheWholeWaitForTheRestore was red. ConfigRoundTripTests' four arms that read a Firefox launch, SettingsHoldBackTests' two held-back texts and SessionCloseTests.AResumeReopensTheTabsThatWereOpenWhenTheBrowserWasClosed changed with it, each red in one of those first two runs: the first two against the four files, and the last against SessionManager.cs alone, when it listed three tabs with the blank page current.
The +6 of the maintainer's 154 c to 160 c. DedicatedThreadTests.AMoveIntoPlaceWaitsOutAHolderOfTheFileItReplacesAndAnswersAnyOtherFailureAtOnce, watched red on 2026-10-13 in a filtered run with the waiting move's budget made zero and again with ReplaceFileAsync made one move; HouseRuleTests.EveryMoveIntoPlaceWaitsOutAHolderOfTheFileItReplaces, red against the five writers as they stood at 40911cbb, naming the five and nothing else, and .NoPollReadsARecordTheProductMovesIntoPlaceExceptThroughItsOwnReader, red against HomeTests.cs as it stood there, naming the poll's two reads and nothing else; SuiteCoverageTests.ATestHostsSessionEndRemovesTheToolListRecordKeyedByItself, red with the session end's removal taken out, its child test host's planted record left; .TheClearanceNamesAToolListRecordATestHostLeftBehind, red against the snapshot script without its eleventh reading and against the script of 40911cbb; and .TheSessionEndsRemovalTakesTheRecordsOfItsOwnTestHostAndNoOther, red with the removal taking every file of the folder, a record of another client among them.
The +13 of the maintainer's 127 c. The six arms of RelayTests.ToolListRecord.cs and the seven of ToolListRecordTests, each watched red on 2026-10-13 in a filtered run. With the product code of fb36712e reversed, which left the record written and the decision Q261 b's, three were red: RelayTests.AClientThatListedThisSameListThroughAnEarlierRelayHasItsFirstCallPassedOn and .AClientThatListedThisSameListHasItsFirstCallHeldWhenThereIsNoBackgroundYet on the refusal they were sent, "because a client whose list is this relay's list was told its list predates the server" and "because the first call was refused, not held", and .ARecordThatCannotBeReadIsNoneAndTheFirstCallIsRefusedAsQ261BRefuses on the log line no relay wrote. Each of the other ten was red against one plant in the tree as built: .AClientThatListedADifferentListIsRefusedOnceAsQ261BRefuses with every record of a digest of the right length letting the call through; .TheRecordNamesTheBytesTheClientWasAnsweredAndOnlyAnAnsweredListWritesIt with the digest taken from the second byte on; .AListWhoseRecordCannotBeWrittenIsAnsweredAllTheSameAndTheFailureIsLogged with the failed write left out of the log; ToolListRecordTests.ARecordIsFoundOnlyByTheClientProcessAndTheCommandLineThatWroteIt with the key made without the command line; .AWriteRemovesTheRecordsOfClientsThatHaveGoneAndNothingElse with no record of a client that has gone removed; .AWriteThatFailsRemovesTheRecordItCouldNotReplace with the record of the list before kept, "because a write that failed left the record of the list before it"; .AFileThatDoesNotHoldAWholeRecordIsNone with an upper-case digest read as one; .WhatOneRelayWritesTheNextRelayOfTheSameClientReads with a second write that does not replace the first; .ARelayThatCannotReadItsClientWritesNothingAndReadsNone with a key guessed for a client the relay could not read; and .ARecordsFileNameNamesItsClientAndNoOtherNameDoes with a name of two parts read as a record's.
The +2 of the maintainer's 103 e and 147 b. ReleaseCutTests.AReleaseRunGatesTheStampedTaggedCommitAndTheProgramThatShips, watched red on 2026-10-13 as a filtered release run over the branch unstamped and untagged, as the old order ran it, six problems with the missing tag first, and green in a scratch clone of the same commit stamped 9.9.9, tagged v9.9.9 there only, and published and packed by build/New-Release.ps1; and CompiledInFilesTests.TheFreshnessCheckWatchesEveryFileAProductProjectCompilesIn, red against the tree with FreshnessInputs not reading CompiledInFiles, naming the three files it left out. ChangelogPageTests.EveryReleaseTheChangelogHoldsIsReadInItsOrder gained a fixture of each shape and a stamped copy, red at each against a scan that kept an empty [Unreleased].
The +78 of the batches merged on 2026-10-12 and 2026-10-13, measured by the release gate above. 6af910d3 (--list-tests), against the 1360 the gate above executed at 16426ee4: 79 added and 2 retired, matched by name against the TUnit report of a full run at 8cdb3ee4, with the other 1358 in both. Measured 2026-10-12, with the characters the two print differently in a case's arguments, a dot and a dash, read as one. --list-tests).
The +1 of the maintainer's 129 a. StackProfilerTests.AgainstARealChromiumASessionsBrowserStartsNoStackSamplingProfiler, red in a filtered run on 2026-10-13 against the tree with BrowserConfiguration.StackProfilerSwitch declared and left out of BrowserConfiguration.ChromiumArguments: the session's browser ran a StackSamplingProfiler thread beside CrBrowserMain.
The +24 of the maintainer's 55 a, 69 d with b, 70 a to 75 a and 86 a. Twenty-five executed cases added and one retired. Twenty-two were watched red at f4cf5b9c, the commit that added them with the behaviour switched off, 26 of 99 in one filtered run of their ten classes: LinkSchemeTests' three, PageLinkTests' five and LinkStartTests' six; CatchUpTests.TheLastFileWrittenLeavesOutBrowserAisOwnRecordFiles and .CatchUpOnAClosedSessionSaysWhenTheProfileWasLastWrittenAndNotTheRecord, on the record's own "0 seconds ago" (86 a); PageServiceTests.TheListenerStaysUpWhileTheBackgroundRunsAndARestartTakesItsLastPortAgain, in the place of .TheListenerStopsAMinuteAfterTheLastTabLeavesAndTheNextStartOpensANewOne, and .AFirstVisitTradesTheTokenInTheAddressForACookieAndAPlainAddress; SignInTaskTests.TheLinkSchemeFollowsTheInstallAndIsLeftAloneWithoutTheSeam; SuiteCoverageTests.TheClearanceNamesATestPacksLinkSchemeLeftBehind; and PageBrowserTests.InARealBrowserTheFolderButtonsAreLinksAndTheFirstCarriesTheLineAboutTheBrowsersQuestion in Chromium and in Firefox. Four extended arms were red in the same run on what they gained: PageListenerTests.TheGateAdmitsOnlyTheTokenOurHostAndOurOriginAndRefusesEachRuleOnItsOwn and .OverARealSocketEveryRefusalIsABare404AndNoRouteRuns on the cookie, and PageBrowserTests.InARealBrowserThePageConnectsPostsThroughTheGateAndAsksToCloseWhenReplaced in both families on the plain address. The extended arms of RealInstallerTests and RealInstallerTests.Scheduler need a test pack and were not run. The other three came from a reading of the page's words that day and were red at 1785813f, the commit that added them with no fix: LinkStartTests.AFolderTheShellDoesNotOpenIsLoggedAsNotOpenedOnALineOfItsOwn and .AnAnswerNamingNeitherAFolderNorAPickerIsLoggedAsAnAnswerAndNotAsARefusal, and BackgroundVerbsTests.AFolderLinkIsToldWhatAStartIsToldWhileTheBackgroundStartsUpdatesOrStops.
The +21 of the maintainer's 59 b, 61, 62 a, 65 a, 66 a, 67 and 57 a: the home, its CPU and memory, and the after-update notice. Twenty-two cases added and one retired, each watched red in a filtered run against a planted build. UsageTests' ten: six against stubs of the usage model at 9e3d6b0e, and four against a build with the card and the charts switched off and an unreadable figure said as Measuring, at 75489f88; the card arm was red again against the code before fd472513, which put the CPU's figure in the chart's read-aloud name before a reading knew it. BackgroundPageUsageTests' three, against builds that read every member as a server, read no relay and took the reading on its caller's thread. HomeTests' six, HomeBrowserTests.InARealBrowserEachReadingIsWrittenWhereItsFigureStandsAndTheDismissFoldsTheNoticeAway in Chromium and in Firefox, and DashboardTests.TheUpdatePageSaysHowLongAConversationHoldsTheUpdateFromTheRelaysCountdownAndTheHomeCountsWhatHoldsIt, in the place of .TheHomeSaysHowLongAConversationHoldsTheUpdateFromTheRelaysCountdown: against the old card, lede, line and caption with no notice, no reading and no dismiss, and then against each behaviour that build masked, alone.
The +17 of the maintainer's 60, 56 a, 88 c, 92 b and 122 b: the map as variant C, the session page folded into it, and What is new. ConversationsMapTests' eight, ChangelogPageTests' seven and PageBrowserTests.InARealBrowserEachLineNamesItsStateAndAConversationsHalfCarriesEveryStateOfItsSessions in Chromium and in Firefox, each watched red against the tree without its behaviour in the commits that added them first: 03723cd2 and 888af96f, 41dbd00a for a session's CPU and own memory in its panel, 139ac923 for two that a reading of the page's words found, a request to open a trace and an address for what is not yet released on a build that lists none, and e448fc0c for 122 b's two, ChangelogPageTests.AnOlderReleasesLinkToAFileRenamedSinceOpensTodaysFileAtTheBuildsRef and .EveryLinkTheShippedReleasesDrawIntoTheRepositoryOpensAFileItsRefHolds, the second with exactly three findings, the three links of the [1.0.0] section to AGENTS.md, a file v1.0.0 holds as CLAUDE.md.
The +10 of the maintainer's 94 and 119 b. CloseReasonTests' four: .ABrowserThatEndsDuringACallIsOneWarningWithItsExitCodeWhicheverBrowserAiHearsFirst, red with the line left out; .ABrowserThatEndsBetweenCallsIsNoSuchWarning and .ABrowserThatBrowserAiClosesUnderACallIsNoSuchWarning, each red against a line that named it; and .ACallBesideATabCloseThatEndedTheBrowserIsNoSuchWarningAndACrashBesideItIsOne, its clean half red against the tree before 9160e3d6 and its crash half against a fix that left out every call beside a tab close. KeptOnRedTests.ARedArmsFolderIsMovedWholeOutOfTheScratchAndTheFailureSaysWhere, red against a KeepAsync that moved nothing, and .TheKeptRootIsOutsideTheScratchThatTheReclaimAndTheDriversClear, which holds where a kept folder goes and was not planted red. And GateDriverTests.ADriverCopiesEveryKeptFolderIntoItsRunsRecordsAndNamesEachOne for each of the four drivers, 119 b, red against the drivers before they copied, beside the four cases of the arm that holds the line for none.
The +2 of the maintainer's 93 a. SessionLockTests.AContenderThatFindsTheGuardHeldBeforeTheRecordWaitsAtTheGateAndIsToldThePurpose and .AContenderThatFindsAPreviousHoldersRecordUnderANewGuardWaitsAtTheGateAndIsToldTheNewPurpose, red against 8cdb3ee4: the first came back at once with "It records no purpose.", the second with the previous session's purpose.
The +2 of the maintainer's 95 a and c. JobContainmentTests.APidThePortReportedThatNamesAProcessOutsideTheJobIsNeitherWalkedNorCounted and .AWalkedMemberThatHadExitedByItsQueryIsExitedAndCountedNeitherWay, red at 1c4ea1a3, the commit that added them with no fix.
The +1 of the reading of the product's texts of 2026-10-12. SessionListTests.AnAgeOfOneIsWrittenInTheSingular, red at 070cea63 on "Sizes were measured 1 minutes ago".
The +14 is the maintainer's 78 to 82 of 2026-10-12: sixteen arms, and two arms of B4 rewritten into two of them. DedicatedThreadTests' seven hold the thread of its own those decisions run on: .TheCallsTheBanKeepsOnTheFileSystemThreadRefuseACallerOnAnyOtherThread, red with the refusal planted away (80 b, 81 c), and six written with the new type and not planted red, which hold its order, its inline run, its continuations, its failures, its end and the file-system thread's asynchronous members. SessionLockTests.ADisposalArrivingDuringAReleaseAndDeleteLeavesTheLockToItAndNothingLeaks and .EveryWriterIsRefusedOnceAReleaseAndDeleteHasMarkedTheSession took the place of the two arms that watched a wait inside the session's lock, red with a disposal that ignores the mark, with the mark after the gate and with the delete under the in-process lock (79 c). SqliteStorageTests.EveryStatementOnASessionStoreRunsOnTheStoresOwnThreadAndNowhereElse, red with one statement's check taken out and with the work run on its caller's thread, and SessionLockTests.AStatementThatWaitsInTheStoreHoldsNoOtherCallerOfTheSession, red with the lock held across the statement, at 4m 04s, and with the directory let go before the close (82 b). SessionListTests.AListingAnswersSizesFromTheBackgroundMeasurementAndSaysHowOldTheyAre, red with every listing measuring again, and DashboardTests.TheStoragePageSaysHowOldItsMeasurementIsOnceItIsNotFresh, red with the page saying when and never how old (80 c). StdioChannelTests.EveryFlushIsWrittenByTheOutputsOneThreadInTheOrderTheFramesWereWritten and .AFrameWrittenWhileAStuckClientHoldsTheLastWaitsWithItsCaller, red against the pump as it was, and StdioPipeKindsTests.ReOpenFileStillRefusesBothClientsPipeKindsOverlapped, red with a kind that reopens put in Claude Code's place (78). Added 2026-10-12 by addition, at the maintainer's 91 a of that day: the six were planted that day, one defect at a time in DedicatedThread or FileSystemThread, each run alone and then green again with the plant reverted. .WorkRunsOnTheThreadOfItsOwnInTheOrderItWasHandedOver went red with the check for an item handed over from the thread inverted, so every item ran on its caller, and 2 times of 3 with each item handed on to the pool, which ran them on 6 and on 7 threads; it stayed green with the items taken newest first, which ran them in the order 0, 49, 48, 47 and on, because Task.WhenAll hands back each result in its own task's place whatever order the work ran in, so its order half cannot fail as written. .AnItemHandedOverFromTheThreadRunsAtOnce went red with that branch taken out. .AnAwaitingCallerContinuesOffTheThread stayed green 10 times of 10 with the item's completion made without RunContinuationsAsynchronously: a probe in the same build found no context and the default scheduler, and read against .NET 10.0.12's own source that means the thread had completed the item before the arm's await was registered, which queues the continuation to the pool with the plant in or out. .AFailureReachesItsCallerAndTheThreadRunsTheNextItem went red with a failure handed back as a success, and at its hang detector with the item's boundary moved out to the thread's loop, so a failure ended the thread. .ACompletedThreadRunsWhatItHoldsEndsAndRefusesMoreWork went red with work handed over after the end taken and not refused, and at its hang detector with the end never reported; it stayed green 6 times of 6 with Complete dropping what the thread held, and a probe found the held item already out of the queue when Complete was called in 5 runs of 5. .EveryAsynchronousMemberMakesItsCallOnTheFileSystemThread went red five times, once for each member moved to a pool worker with Task.Run, each refused by Here at that member. The order half, the continuations arm and the dropped item are reported for his decision. Added later on 2026-10-12 by addition, at the maintainer's 111 to 114 of that day, each in his words verbatim "your recommendation": all seven are planted red since a18746e8, which rewrote the arms that could not fail as they were measured. .WorkRunsOnTheThreadOfItsOwnInTheOrderItWasHandedOver reads the order from run numbers its work takes (111 a), red with the queue taken newest first, which ran the work 0, 49, 48, 47 and on, and still red with the check inverted; .AnAwaitingCallerContinuesOffTheThread makes two hundred hand-overs on the running thread and checks that the item's task carries RunContinuationsAsynchronously (112 a and b), both halves red together 5 times of 5 with the completion made without it, a continuation landing on the thread at the first hand-over; and .ACompletedThreadRunsWhatItHoldsEndsAndRefusesMoreWork hands over a thousand items before Complete (113 a), 999 of them still queued when it is called, red at its hang detector with Complete dropping what the thread held and with the end never reported, and red with work after the end taken and not refused. Each was green again with its plant reverted.
The +17 is the maintainer's decisions 54 and 63 and his 64 c of 2026-10-11, ten arms, and seven arms of the release work of the same day. The ten: GateDriverTests.ADriverFinishesWhileAChildItsRunLeftBehindStillHoldsTheRunsOutput, four drivers, and ClientRunTests.AClientAnArmStartedLeavesNoProcessBehindOnceTheArmEnds, red at 50d58c6d, the commit that added them with no fix, five of five at the ten-minute hang detector; StarvedPoolTests' three, red against 39cddc6c with the arms on the old calls, ThePipeClientGetsItsAnswerOnAPoolOfOneWorker at the hang detector, TheLockRetryWaitsOutAHeldFileOnAPoolOfOneWorker on "The process cannot access the file" when its budget ran out, and AChildsOutputIsReadOnAPoolOfOneWorker at the hang detector once f2867b52 gave its quiet read a head start, since as added it passed; HouseRuleTests.EveryFileStreamInTheTreeIsOpenedForAsynchronousIo, planted red against this tree with one destination opened FileOptions.None; and RestartRegistrationTests.AProcessThatHasExitedReadsAsGoneAndOneThatRunsReadsAsNotRegistered, red at d8b2e708. ProcessLogTests.WaitForExitAsyncWaitsForTheEndOfBothRedirectedReaders took the place of .EveryTimedWaitForExitIsFollowedByABareOne, whose rule decision 54 removed, and holds a property of the platform the rule rests on now, which no BrowserAI code can be planted to break. The release work's seven, whose reds are that work's own record: UpdateToastsTests.AnUpdateThatInstallsAtOnceRaisesNoToastBeforeTheInstalledOneAndOneThatWaitsRaisesTheReadyOne of 0a1701ad, the two of 225b427a, DashboardTests.TheHomeSaysHowLongAConversationHoldsTheUpdateFromTheRelaysCountdown and .AWatchThatCannotBeReadIsLoggedAsItselfAndNotAsWhatHoldsTheUpdate, UpdateToastsTests.TheInstalledToastSaysWhatTheEndedRelaysAppsNeedAndCarriesNoLineWhenNoneNeedsOne of 778d87aa, ProvisioningTests.APollTickThatArrivesAfterItsTickerIsDisposedDoesNothing and PageServiceTests.ALingerTickThatArrivesAfterTheTabsAreDisposedWakesNothing of 01167b7c, and ParentProcessTests.AProcessThatHasExitedReadsAsGoneAndOneThatRunsReadsAsItsParent of f2868343. Added 2026-10-12 by addition, at the maintainer's 91 a of that day: ProcessLogTests.WaitForExitAsyncWaitsForTheEndOfBothRedirectedReaders was planted at its own call, the one place a defect can go since no BrowserAI code is on its path, and stayed green 25 times of 25: 10 with a wait of WaitForExitAsync's shape less its two waits for the readers' end, read in .NET 10.0.12's own source, 10 with the timed overload the ban replaced run through Task.Run, and 5 with that overload called on the arm's own thread, the two timed ones each under a suppression for its own runs alone. The probe writes its report and reads its input to the end after its last line, so its reader had every line before the exit was signalled, and the arm cannot fail as written. An arm that starts the readers once the child has ended went red 10 times of 10 under two of those waits, reading 0 of 100 lines, and green 5 times of 5 with WaitForExitAsync; which of them the suite keeps is reported for his decision. Added later on 2026-10-12 by addition, at the maintainer's 114 a, in his words verbatim "your recommendation": planted red since 49faef75. The arm hands its child the end of its input before anything is read, awaits the child's end while no reader exists, then starts both readers, makes the wait under test, and reads the count before it awaits anything else; the child writes 100 lines into the pipe and ends with no reader. Red 5 times of 5 with that wait replaced by one of WaitForExitAsync's shape less its two waits for the readers' end and 5 times of 5 with the timed overload called on the arm's own thread, reading 0 of 100 lines each time, and green 5 times of 5 with the plants reverted. a18746e8 had asserted the exit code before reading the count, and planted that way it stayed green 15 times of 15: the awaited assertion gave the readers time of their own. The arm's remark that the timed overload would make it red is corrected below that remark, by addition.
The +25 is the maintainer's 40 to 46 and 47 b of 2026-10-11, the release script's version step and the PowerShell gate drivers' test pack, each watched red against the code before its fix. RelayTests.ARefusalOfAConnectionThatSentNoFirstMessageIsLoggedByItsKindAndSentence and ReleaseScriptTests.TheVersionIsDerivedInACheckoutThatWasNeverRestored, red at b4dd0dcc, the commit that added them with no fix, on a frame that "could not be read as JSON-RPC" and on "Could not derive a version"; ReleaseScriptTests.AGateWhoseTestPackStopsWithAnErrorSaysSoAndLetsGoOfTheLock, red against the drivers as they were, which ended with the release script's error and printed no abort line; and DashboardTests' sixteen, BackgroundPageFoldersTests.EveryIndexedSessionAndEveryHeldOneIsWeighedAndAJunctionInsideOneIsNotFollowed, BackgroundUpdatesTests.TheWatchSaysWhereUpdatesComeFromAndWhenTheLastCheckBegan, PageBrowserTests.InARealBrowserTheMapDrawsItsLinesAndANewStateKeepsTheFocusAndTheChoice in Chromium and in Firefox, and the two new rows of UpdatePageTests.AnAppIsCalledTheSameOnTheUpdatePageAndTheConversationsPage, red in one run against the page before this build, 47 failed of 85, but for the two rows of DashboardTests.HowTheBackgroundStartedIsReadFromWhatItsTaskHandedIt that read a start nothing named, which are its controls and passed.
The +2 is the maintainer's 34 d, 2026-10-11, both arms watched red against the code before its fix. BackgroundServerTests.AFirstMessageIsWaitedForAsLongAsARelayHoldsACall, red at 5ac55a7b, the commit that added it with no fix, against the bound of 10 s: "Expected to be equal to 00:02:30 but received 00:00:10"; and BackgroundServerTests.AConnectionWhoseFirstMessageHasNotComeWhenTheBoundRunsOutIsRefusedAndClosed, red at the same commit against the close with no frame: "The background closed this connection with no further frame." Both run the bound on the rig's clock, so neither waits out the 150 s.
The +8 is the evening of 2026-10-10: the standard location of the maintainer's 21, the bare project name of his 30 and the read of RegisterAI's output, each watched red against the code before its fix. StandardLocationTests' six: AShippingCopyOutsideTheStandardFolderIsRefusedAndNothingElseIs, red against a judgement that refused nothing, which let the copy outside the standard folder through; ARefusedCopysHooksSetNothingUpAndItsUninstallTouchesNeitherTheDataRootNorTheClients, red against the hook without the judgement, whose install refused nothing; AnInstallThatSetNothingUpEndsWithItsOwnCodeAndNoOtherHookDoes, red against a hook that ended with Velopack's zero; ARefusedCopyIsToldInABoxAtAPersonsStartAndNowhereElse, red against a start that told nobody; ARelayInARefusedCopyAnswersEveryCallAtOnceWithWhyAndTheRemedy, red against a finder that went on to look for an updater and a task; and TheRootJudgementRefusesARefusedCopyBeforeItAsksTheProfile, red against the judgement of the profile alone, which served the copy. InstallerSettingsTests.TheShippingInstallReadsNoDataRootFromTheInstallerOrFromItsTask, red on the installer's BROWSERAI_ROOT coming back for the shipping pack; and RegisterAiTests.ADocumentRegisterAiWroteBeforeItExitedIsReadWhileAProcessItLeftHoldsItsOutput, red in the gate's own words, "it exited 0 and printed no document BrowserAI could read". Three arms were renamed for what they hold, each red against the ${LOCALAPPDATA} spelling first: RegistrationTests.TheProjectScopeCommandIsTheBareNameForBothClientsWhereverTheInstallIs (previously TheProjectScopeCommandIsPortableAndExpandsToTheDefaultInstall), UserPathTests.AProjectEntryNamesTheServerAloneForBothClientsAndSaysWhatItFinds (previously ACodexProjectEntryNamesTheServerAloneAndSaysWhatItFinds) and RegisterAiTests.AProjectRegistrationGivesBothClientsTheBareNameAndTheFolderItIsFoundIn (previously AProjectRegistrationGivesEachClientItsOwnSpelling).
The +10 is the afternoon of 2026-10-10: six arms of the toasts work and four of the texts polish, each watched red against the code before its fix. The toasts work's: CatchUpTests.AHeldBackResumeIsListedAsHeldBackAndNotAsFailed, red against the row settled failed, which the answer listed as FAILED; UpdateToastContentTests.NoCommandVersionOrQuotedNameIsSplitAcrossTwoLines, red against the morning's wording, which split /mcp, the installed version and a title across two lines; AfterUpdateTests.ARestartWithNoVersionOfOursIsThisBuildsInstallAndNoPersonsStart, red against the argument alone, which read that restart as no after-update start at all; RegisterAiTests.AProjectRegistrationOrRemovalThatIsNotDoneIsToldWhatFixesThatProjectsFile, red against a registrar whose eight answers carried the user-scope add line; and RelayTests.AnEndItsOwnVersionCannotReadIsABugAnsweredAtOnce and .AnEndAnotherVersionWroteIsHeldAndNamedByThatVersionAtTheDeadline, red against an engine that held the first as it holds a clean end and answered the second with the task's state. The texts polish's: ErrorCatalogueTests.TheBrowserNotInstalledRowIsEmittedByABrowserCallWhoseInstallFailed, red on The browser runtime for '...' did not start for a session that was open and only missing its browser; SettingsHoldBackTests.AResumeOfASessionAnotherBrowserAiHoldsIsRefusedAtOnceAndNotHeldBack, red on Held back once for a session another BrowserAI process holds; BackgroundServerTests.TheBackgroundsLinesSayHowItWasStartedAndWhatARelayWasNotToldInWords, red against $(Arg0), first-run and browserai/called-off as they stood; and InstallRootScopeTests.TwoRootsNeitherOfWhichCouldBeSettledAreSaidInOneLine, red against the two lines joined, which said It is serving anyway twice. CloseReasonTests.AnotherClientsCloseNamesThatClientOnce is .AnotherConversationsCloseNamesItsClientAndFolderAndNeverAPid since the polish, red on pid 4242, and moved no count. For the toasts', the pages', the errors' and the log's texts every arm that held one was changed first and run against the old words, 22 red and then 10 more, and moved no count; the model texts' arms were changed with their texts, and their three code points were planted red first.
The +9 is round 2 of the texts review, 2026-10-10: nine arms, each watched red against the code before its fix, and the gate took two rounds. ToolVerdictTests.ADenialIsAJudgementWithNoWordsOfItsOwn, red while the deny arm built a refusal of its own; ProvisioningTests.AProvisioningThatThrowsIsToldInItsOwnWordsWithNoTypeName, red on InvalidOperationException in the detail; ErrorCatalogueTests.TheBrowserServerEndedDuringTheCallRowIsEmittedByAChildThatDiesUnderACall, whose record half was red against a forwarding path that kept the exception as the row, System.IO.IOException: The server shut down unexpectedly., and whose answer half LosslessPassthroughTests' mid-call death arm planted red; RelayTests.ABuildThatIsNotInstalledIsNeverSentToTheStartMenu and .AnInstallWhoseTaskHasNoNameIsToldToInstallAgainAndNotSentToTheStartMenu, red on the Start Menu sentences; RegisterAiTests.AnUnregisterThatIsNotDoneOffersTheLineThatRemovesTheEntry and .AnotherInstallsEntryInAProjectIsAnsweredWithWhatFixesThatProject, red on the add line offered for a removal and the user-scope line offered for a project; ProcessLogTests.EveryLineOfTheBackgroundsAndTheRelaysCategoriesHasAnEventIdOfItsOwn, red on BrowserAI.Background[1] and BrowserAI.Relay[1] each naming several lines; and BackgroundServerTests.TheStopsWaitCountsOnlyConnectionsStillOpenAndWithNoneWritesNoLine, red on the 0 connection(s) and, watched on its own, on the 1 for a connection that had ended. Ten arms that were already there changed with the round and went red the same way, and moved no count. Round 1, at c29aa932, read 1282 of 1284 from PowerShell and 1284 of 1284 from Git Bash: BackgroundStopTests.AStopNobodyAnswersAndOneThatIsRefusedAreNotAsked's refusing stand-in had not answered when the stop's 10 s ran out, and ClientReconnectTests.ABrowserAiRegisteredInCodexServesACallAndLeavesNothingThatHoldsAnUpdate found the relay its positive control reads already gone, each green in the other half, in the PowerShell half run again and three times alone, and each an open row of the hazard index. That run again read 1281 of 1284, the catalogue's census skipped behind the new arm, which had read its row in flight, the race SessionLogTests documents, and it met ScreenshotLimitTests' real-Chromium red a second time, answered now in the round's words. The arm reads the row once it has settled since 87bc65e0, planted red against a settle delayed by three seconds, and round 2, at 87bc65e0, is the gate cited above.
The +85 is the one-binary build's second night, 2026-10-10: five pieces of work and their reviews, every move below piece by piece, counted commit by commit with the suite's own predicate, one case per arm or per [Arguments] row, which reads 1190 at e78c1b53 as that gate did; and the gate took three rounds. Round 1, at 1296bab5, read 1272 of 1275 from PowerShell and 1271 of 1275 from Git Bash. FlatOutputTests.NoTypedFolderEverAppearsAndTheOutputRootStaysFlat still held a session to three folders where the maintainer's 4.1 a gives it a fourth, temp, and SessionToolTests.AnAbsentEmptyRelativeOrMalformedDirectoryIsRejectedByBothInitAndResume still looked for the .NET name String where the texts review's #26 names a wrong kind in words; neither arm was in the filtered runs those changes were checked with, and both hold the decided behaviour since 22557eb2, the first watched red against a product that put the child's temporary folder outside the session again and the second read against the refusal before #26, "it arrived as String.", which its new words do not match. ReleaseScriptTests.NothingElseInTheReleaseDirectoryIsPublished found no release files at the top of a fresh worktree's Releases\, which held only the gate's own test pack until the main checkout's were copied in, as the night's rules say. And from Git Bash alone, CloseOrderingTests.AShutdownThatMeetsAnIdleCloseStillWaitingLetsItFinish asserted that the session's double had stopped the moment the shutdown returned, which three more arms of the file did too; the four wait for the stop since the same commit, and with the double's loop planted to end 500 ms after its pipe the four as they stood went red and the four waiting did not. Round 2, at 22557eb2, read 1275 of 1275 from PowerShell and 1274 of 1275 from Git Bash: ScreenshotLimitTests.AgainstARealChromiumAPageOnePixelPastTheLineIsRefusedAndOneAtItIsNot had its second full-page capture of a 16,385 px page cut short, its session closed under the call, which is a new open row of the hazard index; it passed eight times alone and in that Git Bash half run again at the same commit. A dry run of the deploy's pack then found build/New-Release.ps1 stopping at its first step over any current drift check, fixed in 0ec1131d with the drift arm's positive control planted red, which moved no count; round 3, at 0ec1131d, read 1275 of 1275 from both shells and is the gate cited above. Forty-nine are the naming work's, the names of the clients' conversations (1.1 c to 1.5 a), the texts review's #85 and the middle step of 1.4 a. Thirty-nine came with the names: eighteen ClientRecognitionTests cases, nine of them rows of the arm that reads a session id off a command line only where an option names one and eight of the arm that tells a VS Code tab from claude -p; seven ConversationReaderTests, over the order of the sources, a torn file, a record under another project folder and a Codex index; four ClaudeCodeTitleTests over the extension's title rule; two BackgroundServerTests; and one each in BackgroundUpdatesTests, InheritedEnvironmentTests, PageServiceTests, ProcessLivenessTests, RelayTests, UpdatePageTests, UpdateToastContentTests and UpdateToastsTests. They were watched red against 47 plants, one defect each, which also reached the seventeen ClientRecognitionTests rows nobody had watched fail (8 a). Five are #85: the four rows of UpdatePageTests.AClientIsCalledTheSameOnTheUpdatePageAndTheSessionsPage, whose two nameless rows went red against the old sessions page wording, and ConversationReaderTests.ATitleIsShownOnOneLine, red against a reader that kept a title as it came. Five are a Codex thread's first message, two ConversationReaderTests, two CodexStateTests and one BackgroundServerTests arm, red against fourteen plants. Two are the toasts and pages work's net, eight new and six retired. Its page fixes brought four: PageRegistrationTests.RegisterAndRepairKeepTheDataRootTheInstallRegistersWith and .TheStatusPageNamesTheWholeCommandAClientStarts, UpdatePageTests.AHeldOrInstallingUpdateAlwaysNamesItsVersion and UpdateToastsTests.TheToastsAreHandedWhatHoldsTheUpdateAndNothingThatInstalls, each red against the code before the fix; two arms were rewritten under new names, the sessions page that offers no close (17 a) and the update page whose read failed; and two were retired with a remark where each stood, the install refused when nothing reports holds and the sessions page's old close. Deleting the dashboard's own update machinery (9 a) brought PageServiceTests.TheOldUpdateActionsAreActionsThePageDoesNotHave, red against the code before the deletion, and retired the four arms of the page's check, offer and install. Q308 a brought three, BackgroundUpdatesTests.ARollbackOnOfferIsReadAsOlder, UpdatePageTests.AnOlderVersionIsSaidToBeOlderOnTheUpdatePageAndTheStatusPage and UpdateToastContentTests.AnOlderVersionsReadyToastSaysItIsOlderThanTheInstalledOne, each red before it was built. Two are the session work's, from its sixteen page fixes: CloseReasonTests.AnotherClientsCloseNamesThatClientOnce and ErrorCatalogueTests.TheBrowserRuntimeRowForAChildThatListsNothingEndsItsReasonAndNamesNoType, with SessionHostTests' refused-once arm rewritten under a new name, planted red four ways: the kinds named by .NET again, a reason without its full stop, the type name put back and another client named twice. Seven are the small fixes', the maintainer's 2.3 a, 4.1 a and 5 a, his rule that a release takes only the latest of every dependency, and #47 and #60 of the texts review, every one watched red. UpdateToastContentTests.TheHoldersLineFitsTheBannerAtTheLongestRealisticCounts holds every count up to 99 of each kind to the 54 characters the banner showed whole, red at 62 against the old wording. CatchUpTests.TheBrowsersTemporaryFolderIsNamedWithWhatItHoldsAndGoesWithTheSession was red against an answer that named no temporary folder; SessionCloseTests.ASessionChildsTemporaryFolderIsAVisibleFolderOfItsSession is the old temp arm renamed and re-pointed, red against the instance directory's folder, and the restore arm's real-browser read of the folder went red in both families. ProvisioningTests.AFailedInstallRecordsTheInstallersWholeErrorLine was red with the record beginning inside the first frame, as on 2026-10-08, after a first draft whose fixture was too short went red on its own precondition. ScratchReclaimTests.AnotherTestHostsReclaimTakesOnlyTheFoldersWhoseOwnersAreGone was red when the second host's pass deleted the first host's live folder. ReleaseScriptTests.TheDriftCheckRefusesAStaleStampAndEveryDriftedRowAndPassesACurrentOne was red on the missing script, and .AReleaseIsRefusedFirstWhenTheDriftCheckIsNotCurrentAndATestPackDoesNotReadIt on the missing -DriftCheckFile parameter. UpdateToastContentTests.EveryToastsTextShowsWholeInItsBanner measures every toast BrowserAI raises against the banner it shows in, through a GDI measurement it checks against the night's crops first, and was red on 629 toasts against the wording before #47 and #60. SqliteTests.ThePinnedVersionIsTheVersionOfTheVendoredSource is not new; it went red on the swapped source against the old pin. Twenty-five are the fixes' and their reviews', the maintainer's 9 a and 10 b and the texts review's groups 7 and 8, each planted red with its behaviour taken out. Thirteen close the build's seven open hazards and hold its four untested paths: two BackgroundFinderTests arms, the refused root named as the refusal and a later build's end no crash, red against a finder with no refusal of its own and against the old reading, and the exit code of the background a relay held written into its record, red against a finder that never read it; BackgroundRecordTests' refusal read back whole, red against a reader that took an end it could not parse for none; two BackgroundServerTests, a connection accepted while the background stops, red with the stop's list taken before the listener was joined, and a relay whose link fails keeping no other from being told, red twice; two BackgroundStopTests, red against a stop that called every answered stop ended and one that called an answer it could not read nobody running; PersonStartTests' hung background ended, red against a start that verified the stand-in's image against a folder it does not lie under; two RelayTests, a refused root answered at once with its remedy and a first call with no tool list and no background refused at once, red against an engine that answered with the crash sentence and one that held first; SandboxFlagTests, red against the selection that read every payload node; SuiteCoverageTests.TheClearanceNamesATestPacksActivatorLeftBehind, red against the script as it was; and ToastActivationTests.UninstallingOneOfTwoInstallsOfOnePackLeavesTheClassToTheOther, red against the unregistration as it was. RealInstallerTests' refused-root arm moved with them under a new name. Five are BrokenInstallTests (10 b): one toast however many refusals, red against a notice that raised one each time; a session child whose list differs, red against a session host that told nothing; every page's notice, red against a page that rendered none; an open tab told at once, red against a page that pushed nothing; and the click, red against an activator that did not know the action. Five are the texts review's groups 7 and 8, each planted red before its fix went in: two CloseReasonTests arms and one BackgroundServerTests arm over why each session closed when the background ended (#24), a published BackgroundProcessTests arm that stops a background with the uninstall hook's own stop and reads both sessions' records, and RegisterAiTests.EveryLineAPersonRunsByHandCarriesTheArgumentsTheRegistrationWasGiven (#138 and #139). One is SaturationTests.TheBookkeepingReclaimTakesOnlyWhatThisRunsBackgroundMadeWhileItLived, red against the pid-only selection. Seven are the texts review's fixes, the person's start and the sign-in task in the texts review's words: four PersonStartTests, one RelayTests and two SignInTaskTests arms, each red against the text as it was. The deletion of what nothing called moved the count by -6: the four UpdateTests arms of the live-instance census and the three ErrorCatalogueTests arms of the proxy's update refusals were retired with a remark where each stood, and the reclaim half of one carried over as UpdateTests.AMarkerThatCannotBeOpenedIsLeftAloneByTheReclaimAndNamedInItsReason, red against a walk that removed a marker it could not settle. The planting pass added nothing and planted red every arm of the one-binary build that had never been watched fail (8 a).
The +186 is the one-binary build of 2026-10-08 and the night after it, with two arms of 2026-10-04 that landed after ec7b841, and every move is below, piece by piece; counted commit by commit with the suite's own predicate, one case per arm or per [Arguments] row, which reads 1004 at ec7b841 and 1079 at 079e3d1c as those gates did. Two are the Q371 work's of 2026-10-04 (4 a): CatchUpTests.EveryFileThatCanHoldSomethingSensitiveIsNamedKindByKind, watched red with ten of its lines missing and the arm on the four older kinds reading the old trace line, and CatchUpTests.AProfileWithNoCookieStoreIsNamedForWhatElseItHolds, red on the old line. Eight are step 1's. One is ProtocolSplitTests' arm sending Claude Code's two opening frames to the published binary, red against the unpinned server, which answered server/discover offering 2026-07-28; the arm about an older revision now holds the 2025-11-25 answer, red against the unpinned build's 2025-06-18. Seven are the tool list answered from the binary: six BuiltInToolListTests and ErrorCatalogueTests.TheInstallIsBrokenRowIsEmittedByASessionChildWhoseListDiffers, the catalogue arm red against a tree with no check, the published arm and the two that read the snapshot and the verdicts out of the binary red against 6694dda8, the build that still started a child of its own. Thirty-nine are the session work's, each planted red with its behaviour taken out. Three come with browserai_close (D1, F1 a), one each in ErrorCatalogueTests, SessionCloseTests and SessionPolicyTests, red against a tree that listed the tool and did not answer it. Five come with every session's own idle countdown (E2, F2): IdleCountdownTests' six arms, planted red with each behaviour taken out in turn, less BrowserIdleTimerTests.AHeadedSessionIsNeverIdleClosedAndItsConfigTurnsUpstreamsTimeoutOff, whose premise E2 reversed. Fifteen are VisibleInputWatchTests (F4 a), red against a skeleton whose check did nothing or whose registration started no timer, a check that told every session about any input, one that took the window in front for activity, one that skipped the tick reads on an unknown window, one that compared the ticks without the wrap, a manual-reset timer and a last-input read with no size set. Two more IdleCountdownTests arms wire that check into sessions, red against a session that never joined it and a countdown that skipped its own last read. Fourteen come with the four stated settings and the hold-back (D2 b, F1 a, F2 d, E2, F5 a): SettingsHoldBackTests' twelve and two ErrorCatalogueTests rows, planted red one behaviour at a time. Thirty-nine are the toasts and pages work's. Thirty-seven are the update toasts, their activator and the dashboard's update and changelog pages, T and H1-T a of 2026-10-08, and every one was watched red. Ten are UpdateToastContentTests, red against stubs that returned nothing: the ready toast a reminder with Install now and Wait for inactivity, its countdown bound to the progress element's four fields and nothing else, the installing, installed and failed toasts with their buttons, a version and a path written as text, the countdown the longest still running with the holders counted by kind, the bar starting again when activity moves the end later, a holder with no countdown saying what the wait is for, several of a kind and a client whose reconnect nobody could tell, what is left in whole seconds rounded up, and a click read back from its arguments. Seven are UpdateToastsTests, six red against a driver whose four calls did nothing and one against a memory file that kept nothing: the ready toast raised once a version and counted down each second with a rising sequence number, the countdown stopping once Windows no longer has the toast, each later toast removing the others first, a version the person chose to wait for raised with no banner, an update that stops waiting taking its toast with it, what Windows refuses recorded and nothing thrown, and the wait kept in one file. Five are ToastActivationTests, red against a derivation that gave nothing, a registration that wrote nothing, a click that opened nothing and an activator that dropped what it was handed: the class derived from the application id and pinned to a value a second implementation computed, both registry values written under a scratch key and only one id's taken back, each click's page or wait, a click through the activator's own class object, and only COM's start counted as an activation. Four are ToastInteropTests, red against two slots swapped in IToastNotification2, one digit off in the map's id and IClassFactory's two slots swapped: the ids and slot order against System32\WinMetadata, the map Windows hands out, a real toast object composed and read back without being shown, and the activator's two interfaces against CsWin32. Six are UpdatePageTests and four are ChangelogPageTests, red against renderers that rendered nothing, a section never found, routes and a button that answered nothing and a watch that pushed nothing; the refusal of the install button where nothing reports what holds an update was planted separately, by a guard that accepted it, and so was the changelog the build carries, by a project that embedded none. One is ToastPageStartTests, the start and the pipe verb each page is opened by, red against a start that knew neither page name and a pipe that parsed neither verb. Two are the status page's update section under the background: UpdatePageTests.TheStatusPageSaysWhatTheBackgroundHoldsAndNeverThatNoFeedIsSet and .AStatusTabIsSentANewStateWhenAnUpdateBecomesHeld, both red against dcded85b's product code, which told every installed BrowserAI that no feed was set. Seven are the records work's numbers index (F3 a): NumbersIndexTests, which holds kb/numbers.md and the code against each other in both directions, refuses a duration written as a number anywhere under src/ outside the six named classes, with a control for each shape, and holds that every wait a tool call can meet ends inside the stricter client's limit; planted red one plant at a time, a row taken out, a value changed, a row for a member nobody declared, and a literal written back into CoordinatorProtocol. Ninety-one are the architecture work's and its reviews', the one executable, the resident background and its relays, and the settings as arguments (D7 a, S a, p a, R, D11 a, D12 b, D13 a, H1 a, H2 a, D9, T). Three are step 2: AppBinaryTests' mode arm, red against the tree before the change; AppBinaryTests.TheSuitesStdioClientNeverStartsThePublishedBinaryAsAPersonsStart, red with RawStdioClient.RefusalFor planted to refuse nothing and, read off the source and never run, with Start planted to stop asking; and a FirstRunProvisioningTests arm on the wait that ends at the provisioner's first record of an install that gave up, red with the log reader planted to find nothing. Twenty-four are the update core, BackgroundUpdatesTests' eighteen, AfterUpdateTests' three and UpdateSourceTests' three, each red against a defect planted in the finished code. Thirty-nine are the relay's own suite, RelayTests across its five files, each red against a defect planted in the engine for it. Sixty-three went with what steps 3 and 4 deleted, as whole files (CoordinatorTests 10, CoordinatorWakeTests 3, ServerPipeTests 11, SessionHostAccessTests 4, SessionHostBoundsTests 4, SessionHostCoordinatorTests 9, SessionHostProcessTests 2, SignInStepTests 5, UpdateInProgressTests 3) and as ten UpdateTests and two PageServiceTests arms that keep a RETIRED remark; three BackgroundProcessTests arms came in, against a published background and real Chromium, and were watched red the next morning on request, the two session arms against a background that stopped once its last relay went and the kill arm against a background that wrote no record, beside AppBinaryTests' rewritten mode arm, red with IsTheTasksStart false for --sign-in, ToastPageStartTests' rewritten arm, red with --changelog naming no page, and PageServiceTests' rewritten linger arm, red with every stop final. Four port to the update core what five deleted UpdateTests arms held: a check past its budget, a feed that throws with nothing on offer after it, a moving download against a stalled one, and a stop during a check, each red against its own plant. Seventy-one are the first review's in-process arms: BackgroundServerTests 10, BackgroundVerbsTests 2, BackgroundRecordTests 6, BackgroundFinderTests 9, ClientRecognitionTests 23, PersonStartTests 7, ten more in SignInTaskTests and InheritedEnvironmentTests 4, the last holding that no client's messaging token reaches a report, the process log, a child or the background; each was watched red against a defect planted for it, except, in its own words, three of BackgroundFinderTests' four task-state rows and seventeen of ClientRecognitionTests' twenty-two rows, which no plant reached. One is the second review's: InstallRootScopeTests' refusal arm, judged by the background since S a, became two variants, the root by the suite's variable and by --data-root, red against a background that wrote no record and one that served any root; the second review's other moves are carried arms, two BrowserIdleTimerTests and one InstallerHandoffTests arm retired for a premise kept sessions reversed and carried over under new names, each red against its own plant, and SaturationTests rebuilt as a hundred relays and one background, whose new assertions were not watched red; within arms that were, the relay's end-of-input and client-exit records, the half of the refusal arm where a relay answers the crash, and the Codex arm's positive control were not planted separately. One is BackgroundRecordTests' write that waits out a reader, red against a rename tried once. Three are step 5: HouseRuleTests.NoRunningBrowserAiReadsABrowserAiVariable, red against a start that read the variable again, and InstallerSettingsTests' three, the update hook's red against hooks blind to the definition the install saved; and the variable form of InstallRootScopeTests' refusal arm went, since no running BrowserAI reads it. Five are the third review's: SignInTaskTests.ATaskTheSchedulerRefusedStillLeavesItsDefinitionForAPersonsStart, red with the definition written only on a registration, which reverses an assertion the first review had watched red the other way, at the architecture work's decision; RelayTests.ALookWhoseTimerFiresEarlyIsArmedAgainAndTheRelayGoesOnLooking, red against the handler that left its look unarmed when the timer fired before its moment, which 146 of 200 two-second timers did on this machine, so a relay started before its background never reached it; and four arms in RealInstallerTests.Scheduler.cs over the suite's pack installed by its real Setup.exe and started through the real Task Scheduler (D14 b): a person's start served by the resident background, red with the background ending with its listener; a relay answering an update at once and a missing task after its hold, then reaching the background a person's start registered again, red with the finder ignoring the updater and with a person's start that registered nothing; a disabled task named with how to enable it, red with the relay reading it as ready; and a refused data root answered as a recorded crash at once, red against the product as it stood. RealInstallerTests.TheInstalledMainExecutableServesItsPageAndShowsNoWindowOfItsOwn was retired, its subjects gone with the coordinator, and its page, window and minute moved to the first of the four.
The +22 is two pieces of work of 2026-10-04. Nineteen are the behaviour work's, the maintainer's 6 b, 8 b and 9 d. Eight are CloseReasonTests: a person closing a headed window closes the session, and the refusal, the record, the resume and browserai_catch_up say so; an exit code other than 0 reads as a crash or a kill and 0 in a hidden session as the browser's own exit; the caller's own close is recorded with its client and its reason; another client's close is named to the next client; a shutdown records its cause, a client gone or a stop through the pipe, and the next resume says it; an opening no close followed reads as a kill or a crash; a kept window a person closes is recorded as theirs and let go; and a real Chromium killed by its pid closes the session with its exit code. Watched red: seven with the browser's end and the record switched off, and the eighth with the reading of an opening no close followed switched off, which also turned the catalogue's stranded-session row red. The crash arm went red a second way first, losing a race between the answer and the watch, which is now armed before the caller hears the answer. Five are HeadedUserAgentTests: the headed user agent is the hidden one with its product token replaced, the answer is the page the browser dumped, only a hidden Chromium's config carries the switch, a kept answer serves the build it was asked of and no other, and a real hidden Chromium's requests and pages carry the headed user agent, which was watched red against the tree before the switch: HeadlessChrome/155.0.0.0. The other four test the new class and were written with it. Five are ScreenshotLimitTests and one is the catalogue's: a PNG's or a JPEG's size read from its head, an inline Chromium screenshot past the line refused and one at it not, a screenshot in a file judged by the file its answer names, a Firefox session's never read, and a real Chromium page one pixel past the line refused and one at it whole; with ErrorCatalogueTests.TheScreenshotRowIsEmittedByAChromiumScreenshotPastItsLimit. Watched red: four of them and the catalogue's row against the tree before the check, and the Firefox arm against a check that read every family; the header reading tests the new class and was written with it. Three are 5d38dbf's, 2 b, which reached master after the last gate; their own account is in that commit and its CHANGELOG entry.
The +17 is two pieces of work of 2026-10-04. One is RegisterAI's standing rule, the maintainer's words verbatim: "Whenever there needs changing. Create a new stable RegisterAI release and reference that." RegisterAiPayloadTests.TheCommittedStampNamesAStableRegisterAiRelease reads the committed build/payload/registerai.json and holds that it names a release of SixFive7/RegisterAI taken from GitHub, a version of three numbers with no pre-release part, that version's own tag and the tag's page, beside five planted stamps that must each fail and one that must pass. Watched red against the stamp build/Get-RegisterAi.ps1 -From wrote over RegisterAI's release folder: "The stamp's source is 'folder', not 'release'", with no tag and no page. Sixteen are 8b3f5c1's, Q371 c and Q365.1 to Q365.4, which reached master before this gate; their own account is in that commit and its CHANGELOG entry.
The +13 is the close of 2026-10-04: one cap of a minute for every clean close, D4.1 and D4.2; nothing cutting a close short, Q378; and Chromium's crash-restore switch, Q376 a. Ten are CloseOrderingTests, each watched red against the tree as it stood: in process, a resume that met the idle close and a resume that met the caller's own close each answered while the double still held the close, a caller that stopped waiting for its own close had its cancellation sent on to the child, a shutdown that met the idle close ended while the double still held it, and a destroy cut the close short as it always had but said nothing in the session's log; through the session host in process, a kept session's idle close was ended by the next client's resume, a resume that took over a closing session answered at once and said both that the browser had been kept and that BrowserAI had closed it, and a client that went during its session's close let the session go at once; and against a real Chromium whose close the suite's probe held back, in process and in the session host, the resume answered while the probe still held the close, where with the fix the cookie written just before the close is read back after the reopen with both tabs restored. Three are CloseBoundsTests: the cap as twice Chromium's cookie interval, red through the three names it replaced, which read thirty seconds, one second and thirty; a teardown's wait for an idle close in flight, red at the twenty seconds it held; and every wait on a child's exit as the child's own timeout, red against the literal it replaced. One is SessionCloseTests.AResumeAfterTheBrowserWasKilledReopensTheTabsItHadOnDisk, which kills a real Chromium's whole job once a save has come after both pages were open and the profile's Preferences file is on disk, reads that both pages are in the session file, and resumes: red with the new tab page and nothing restored. The -1 is BrowserIdleTimerTests.AResumeThatMeetsAnIdleCloseStillWaitingEndsTheWaitAtOnce, retired, because it held the behaviour the ordering rule forbids; its replacement is the first of the ten, and the retirement is named where the arm stood. Arms that changed without moving the count: the idle close's two cap arms hold the minute, red at thirty seconds; the shutdown arm holds its wedged close to the minute's tick, red at one second; the arm whose caller's close never answers holds the resume waiting it out to the cap's tick, red where the resume answered at once; SessionHostBoundsTests' stop arm holds two minutes, red at sixty seconds, renamed; and the generator's restore arm holds the switch, red without it. Two arms were fixed after their first runs, and each was planted red again as fixed. The kill arm first read the session file while Chromium held it for exclusive reading, and waited out the hang detector with both pages in a file it could not open; it then killed a fresh profile's first launch 3.1 s in, before Chromium had written the profile's Preferences, and the relaunch restored nothing with the switch on, because Chromium takes a profile with no Preferences file for a new one, now a row in HAZARDS.md; and waiting on a write after the second tab's answer alone, it met a gate's load with that save already made. It now opens a third tab to force a save, waits for the Preferences file, and reads the session file once the kill is over. The arm whose close never answers let its second child hold its close too, so the rig's own shutdown waited on a clock nothing moved. The PowerShell half took three runs. The first read 964 of 965: StraySweepTests.AnAbandonedSweepMutexIsAcquiredAndThePassProceedsAnyway found the sweep's machine-wide mutex abandoned on the pass after the one that consumed it, a new open row. The second read 964 of 965 on CoordinatorWakeTests.AServingCoordinatorIsAskedToLookAgainAndNoTaskIsStarted, that open row's sixth red. Run a third time at the same commit it read 965 of 965, which is the figure above. A gate before the kill arm's last fix, at 6a02c20, read 963 of 965 and 964 of 965: the kill arm's wait, the coordinator wake's fifth red, and a census race in SessionHostCoordinatorTests.WithNothingStagedTheCoordinatorStaysWhileItsSessionHostRunsAndStopsOnceItExits, a new open row too.
The +1 is Q379 b, 2026-10-04: RegisterAI is public, and the payload build reads its release with no sign-in. RegisterAiPayloadTests.ThePayloadTakesTheNewestRegisterAiReleaseWithNoGitHubSignIn runs build/Get-RegisterAi.ps1 with no folder, gh pointed at an empty configuration folder and every token variable removed, after a control that gh auth status is signed in to nothing under those variables, and holds that the stamp names the newest release and the placed file hashes to it. Watched red against the script that read the release with gh: exit 1, "gh release view --repo SixFive7/RegisterAI exited 4".
The -8 is the browser tab's second and third steps: registration in the tab with the configuration window deleted (Q319 b), and the sessions page showing what the session host keeps. Seventeen went with the window: thirteen ConfigurationAppTests arms, which were the dialog's callback, close button, icon, owner and page, its update command, its registration actions, its two project removals, the three arms of its background work and the deadline that work was held to; three TaskDialogLayoutTests arms holding the task dialog's structures against Microsoft's metadata; and CoordinatorTests.AShowWhileTheWindowIsOpenRaisesThatWindowAndLeavesTheRecheckWaiting. Four of the thirteen moved to the page's own arms: the one client per action, both project removals and the deadline. Nine are new, and every one was watched red. Four are PageRegistrationTests: each client's section, red with a project file's command written into the page unescaped and again with Q314 b's sentence shown for every client; a button running the registrar for its own client, red with an unregister offered where nothing is registered and again with the registrar's text left out of Show details; a project folder from Windows' picker, red with the entry written into the picked folder's parent; and a picker held open, red with a click taken meanwhile run anyway. PageServiceTests.ACheckTheFeedNeverAnswersEndsAtTheServersOwnTripwire is red with the page's bound a minute past the server's tripwire. Four are the session host's: ServerPipeTests.ADescriptionSaysWhoDrivesEachSessionOrThatItIsKeptAndAnOlderOneReadsAsAServers, red with a description read with no session kept; SessionHostTests.TheHostSaysWhoDrivesEachSessionAndThatOneWhoseClientWentIsKeptAndUntilWhen, red with the host naming no client and again with every session described as driven; PageServiceTests.TheSessionsPageShowsTheHostsKeptSessionsForWhatTheyAreAndOffersNoCloseForTheHost, red four ways, with a kept session said to be nothing, the host listed like any server, a relaying server not given its client's sessions and a close sent for the host closing it; and PageServiceTests.AnInstallStopsTheSessionHostAfterTheServersAndBeforeTheHandOver, red three ways, with no stop of the host, no reopen after a failed install and the host asked with the servers. Arms that changed without moving the count: TaskDialogLayoutTests is AppBinaryTests; ConfigurationAppTests.TheReportCarriesEveryAnswerTheWindowWouldHaveShown is .TheReportCarriesEveryAnswerThePageShows; and HouseRuleTests.EveryFolderPickerIsOwnedByTheDialogThatOpenedIt is .EveryFolderPickerRunsOnAThreadThatAsksForASingleThreadedApartment, red with the picker's thread not asked for the apartment. The gate took two rounds. The first, on d8db27a before the rebase onto option c's merge, read 916 of 917 from PowerShell, the one red the coordinator wake's recheck arm with "did not answer 'recheck' inside 500 ms", which the hazard index records, and 917 of 917 from Git Bash; the second, on the rebased commits with the sessions page's step added, is the count above.
The +30 is option c, Q366 b and Q364, the maintainer's answers of 2026-10-03: a session that outlives the client that drove it. Ten are the session host in process (SessionHostTests), over a fake child: with a connection's end planted to tear down what it drove and every claim granted, seven went red -- a session outliving the connection that opened it, a second open connection refused, the headless and headed sessions kept until their idle close and their window, a child's progress reaching whoever drives the session now, a destroy from another connection refused, and a resume taking a kept session over -- and with the end planted to keep everything and the idle close to let nothing go, the four that let a session go went red after the five-minute hang detector; the tenth, a client that re-dials the host and calls before it lists, went red with the host's connections planted to say the server's sentence. One is the new catalogue row (ErrorCatalogueTests.TheDrivenElsewhereRowIsEmittedByASecondClientNamingASessionTheFirstDrives), red with every claim granted; the census's count of rows moved to 38 with it. Nine are the coordinator's half (SessionHostCoordinatorTests), with a scripted host and windowless stand-ins in a real job, each red against its own plant: the coordinator not staying for its host, the host not stopped before an apply, no second scan after the stop, host queued like any verb, the keeper leaving nothing of its own out of the scan, a stop for an update leaving the keeper open, the keeper's close leaving its job open (after the ten-minute hang detector), --start-host read as an ordinary sign-in, and the sign-in step applying while the host runs. Four are an installed server's search for its host (SessionHostAccessTests), in process with a pipe the arm creates and a scheduler that starts nothing, each red against its plant: a serving host never looked for first, the logon task run with the wrong argument, a task run for a build that is not installed, and a task that did not start waited for as if it had. Four hold every bound the host added to what it is derived from (SessionHostBoundsTests), each red with its own plant: the front's wait back at the chosen 6 s, the host's close off the idle close's cap, the linger off the coordinator's minute, and the host pipe's buffer a size of its own. Two are the published host and front against Chromium (SessionHostProcessTests): a session outliving a front ended the way Codex ends a server, red with the connection's end planted to tear down what it drove, and a host whose death leaves nothing it started running, which was not watched red: planted with each session's job created without kill-on-close it stayed green, because the host's death also ends each child's stdin and @playwright/mcp closes its browser on that, so the arm cannot tell the two apart and its remark now says so; the job half is JobContainmentTests' and, for the coordinator's job, the keeper arm's. The first gate on this same commit read 958 of 959 from PowerShell: ServerPipeTests.AStopThroughThePipeEndsAPublishedServerTheGracefulWay found the stopped server's instance directory still there, the server having logged five milliseconds after its surface child's exit that three entries would not go; the server took the in-process path this commit leaves as it was, the Git Bash half of that gate and both halves of this one were green, and it is not yet recorded as a hazard.
The +17 is the browser tab, Q315 a, and every one was watched red. Four are PageListenerTests, from raw sockets against the listener and its gate: the gate's rules one by one and the same rules over a real socket, both red with the Host check off; what Kestrel answers itself, red with the header limit off; and the address no other socket of this account can take, red with a wildcard bind. Ten are PageServiceTests, each red against a plant of its own: the footer link keeping its referrer, an older offer not said, a folder feed with no release list read as up to date, the install not asking the servers to stop first, a Codex server not warned, an action taking a folder by path, an older tab not told it was replaced, the minute not restarted by a tab that leaves, the tab not told of the apply, and the token written to the log. One is CoordinatorTests.AStartThatMeetsAStoppingCoordinatorWaitsItOutAndBecomesTheCoordinator, red with the stopping refusal not waited out. Two are PageBrowserTests, Chromium and Firefox, red on both engines with the gate refusing a same-origin write, at the check, and again with the replaced page no longer asking to close, at the wait for the tab to go or to have asked, that run's waits cut to a minute. The arm is in its fourth form: the first waited for the replaced tab to go and hung on Chromium for the thirty-minute hang detector; the second opened the page in a new tab and found that tab's history at two entries in Chromium, the blank page it began on and the page; the third replaced a blank tab's one entry with the address and did not connect in either engine within the minute its planted run waited; the kb has what each found. Arms that changed without moving the count: the person's-start arm, renamed, red with no address handed out; the published second-start arm, red with --sessions ignored and again with the address written wrong; and the real-installer window arm, renamed, which was not watched red. The gate took three rounds. Round 1, on 798aa5b, read 917 of 928 from PowerShell and was stopped before its Git Bash half: every arm that starts the test probe failed at the probe's first log line, because the probe runs from the test host's folder and the build no longer copies Microsoft.Extensions.Logging there once the host takes it from ASP.NET Core's shared framework. The probe takes that framework too since, and the four classes involved read 89 of 89. Round 2 read 859 of 928 from both shells, because the probe's project file is an input of the publish's freshness and was edited after the publish, so 69 arms on the published slices refused to run; both slices were published again. Round 3, after the rebase onto d8a0101, was green from both shells.
The +1 is the update refusals' Claude Code remedy, 2026-10-03. ErrorCatalogueTests.AClaudeCodeSessionIsToldWhatATerminalNeedsWhenAnUpdateEndsTheServer provokes both update rows for a client that calls itself Claude Code and holds that each names the restart, the disconnect and /mcp. Watched red twice: against the old still-installing sentence, then, with only that one fixed, against the old update sentence. The Git Bash half took two runs. The first read 911 of 912: CoordinatorWakeTests.AServingCoordinatorIsAskedToLookAgainAndNoTaskIsStarted again saw the coordinator's pipe not answer recheck inside the wake's 500 ms, the third red of that arm on this machine and the open row in HAZARDS.md. Run again at the same commit it read 912 of 912, which is the figure above. A PowerShell half before both, on a worktree payload that did not yet carry RegisterAI, read 901 of 912 with 7 red and 4 skipped, every one of them RegisterAI's absence, and is not counted.
The -32 is step 7 of RegisterAI's plan: BrowserAI's own registration code deleted, and RegisterAI a sixth reviewed upstream (Q350 a). Thirty-five went with the code they drove: CodexRegistrationTests' 14 arms, 19 in RegistrationTests -- step 6's agreement arm among them -- UserPathTests.ABareNameIsJudgedByTheFileItFindsFirstOnThePath, which tested the reader half, and UpstreamReviewTests.TheFiveReviewedUpstreamsAreTheOnesTheDriftCheckResolves, which comes back below under a new name. RegisterAI's own suite holds what the deleted arms held. Three are new: UpstreamReviewTests.TheReviewedUpstreamsAreTheOnesTheDriftCheckResolves, the five-upstream arm for six, red against a test that could not resolve RegisterAI; ReleaseScriptTests.APayloadWithoutRegisterAiRefusesTheManifestAndWritesNothing, red against the manifest script without the registerai block; and SuiteCoverageTests.AChildTestHostLeavesItsParentsScratchAlone, red against the client search as step 7 first wrote it. That search took its scratch homes through ScratchDirectory.Create, the child the filter arm starts read it in its session-end hook and ran the scratch reclaim over the parent's live directories, and the first PowerShell half on this change read 895 of 910. The homes are taken under ScratchRoot.PathAsComposed since, and the gate above is the first round after. Arms that changed without moving the count: ReleaseScriptTests.TheResolvedSetManifestIsEmittedWithAllEightFilesAndWhatEachStates, red against the script without the block, and ThirdPartyNoticeTests' two arms over the new obligation row, red against a publish with RegisterAI.exe moved aside and against a copy of this tree's test pack without it. Not watched red: ClientReconnectTests.ABrowserAiRegisteredInCodexServesACallAndLeavesNothingThatHoldsAnUpdate, which registers its sandboxed server through RegisterAI; three RegistrationTests arms and UserPathTests.ACodexProjectEntryNamesTheServerAloneAndSaysWhatItFinds, re-pointed where they named a deleted type; and seven arms in CoordinatorTests, RealInstallerTests, SignInStepTests and TaskDialogLayoutTests that named Codex's home variable through a deleted class.
The +14 is the switch to RegisterAI, step 6 of its plan -- Q332, Q349 a and Q350 a -- and the gate took three rounds. Round 1, from PowerShell at the step-6 commit before its rebase onto 78906e7, read 935 of 936: UpdateTests.NoProductPathIsResolvedFromAppContextBaseDirectory found RegisterAiTool.Beside falling back to AppContext.BaseDirectory, and the fallback went. Round 2, from PowerShell at 0b76952, read 942 of 943: ServerRegistryReapTests.ADestroyReapsTheDeadDescriptorsSparesALiveOneAndDoesNotWaitForEither met destroy's survivor arm under load, 3 items, and asserted isError was not true, the shape HAZARDS.md's row on a test that demanded a stronger guarantee than the tool it was testing makes names as the one that re-opens it. It now asserts DestroyAnswer.AccountsForWhatItLeftAsync, red with the old assertion against a file the test held open in the closed session. Round 3, at dffe9d4, read 943 of 943 from PowerShell and 942 of 943 from Git Bash: CoordinatorWakeTests.AServingCoordinatorIsAskedToLookAgainAndNoTaskIsStarted saw the coordinator's pipe not answer recheck inside the wake's 500 ms. No other red of that arm is in any of the 166 suite logs on this machine, nothing in this change touches the coordinator, and the Git Bash half run again read 943 of 943, which is the figure above. Corrected 2026-10-03 by addition (previously "No other red of that arm is in any of the 166 suite logs on this machine"): a PowerShell run of 2026-09-29 in the main checkout's own .work\suite read 893 of 895 with the same sentence, found by a search of every log under .work that reports a run's totals, and HAZARDS.md carries the arm as an open row. Nine are in RegisterAiTests: an install over a matching entry of ours left as it is (Q347 a), every answer's status and sentence, five ways of giving no answer through the registrar and the hook, a throwing tool and a refused path, each client's own project spelling, the state read with one run and one more per project, and a RegisterAI that cannot be asked leaving every client unknown, those seven red against stubs that never ran the tool; an image with no folder refused before anything starts, red against the fallback round 1 found; and Q350 a's schema arm over the payload's own RegisterAI, red with this reader planted at schema 2. Three are in RegisterAiPayloadTests: a payload that takes RegisterAI only when it matches its checksum list, red against a missing script; the committed stamp, red with a wrong digest planted in it; and the published slice carrying the payload's RegisterAI, red with it moved aside. Two are in RegistrationTests: the payload's RegisterAI against both real clients in a sandbox, red with every register planted to pass --replace, which breaks Q347 a; and the agreement arm, where the old reader and RegisterAI read six Claude Code states of one file, red with the new reader planted to take a stale entry of ours for a current one. RegistrationTests' four hook arms and ConfigurationAppTests' two picker arms, re-pointed at the fake tool and red against the same stubs; and, not watched red, five uninstall and upgrade arms in RegistrationTests and CoordinatorTests.AShowWhileTheWindowIsOpenRaisesThatWindowAndLeavesTheRecheckWaiting, which take the fake tool where they took the client double, ConfigurationAppTests.TheNearestProjectFileIsFoundByWalkingUpwards, which names the project file through RegistrationClient, and InstallerHandoffTests.TheInstallersVariablesAreClearedAfterVelopackReadsThemAndBeforeAnythingStarts, which finds the hook's launch by its new call.
The +6 is three of the maintainer's answers of 2026-10-03 -- Q367 a, Q368 a and Q371 a -- and a number the remarks had wrong. Three are the idle close asking the browser to close itself first (Q367 a), in BrowserIdleTimerTests: a close answered and the child ended only then, red against the close as it stood, which asked for nothing, and again with the close sent and not waited for; a close that never answers, ended once its thirty-second cap runs out, red against the old close and with the cap armed and wired to nothing, after the five-minute hang detector; and a resume that meets a close still waiting, red against the old close and with the wait deaf to a teardown, when the resume took the idle timer's whole 20 s bound. One is the coordinator's pipe (Q368 a), CoordinatorTests.ACallerThatConnectsToTheCoordinatorAndNeverFinishesBlocksNoOtherStart, red against the one-instance pipe after the whole hang detector. One is a pipe holding 300 callers at once, ServerPipeTests.APipeHoldsMoreCallersThan255AtOnceAndStillAnswersTheNext: 255, the instance count every pipe of ours is created with, is PIPE_UNLIMITED_INSTANCES and no ceiling, measured to 2,000, and the arm was green against the tree as it stood, which already created instances that way, and red with the count planted at 254. One is what tracing says (Q371 a), ModelSurfaceTests.TheTracingArgumentSaysWhatItWritesAndNamesTheToolsThatRecordATrace, red against the old texts with ten phrases missing. Arms that changed without moving the count: the coordinator's second-instance arm, renamed for what it holds, red at 0x800700E7 where it now expects 0x80070005; the idle arm that read no browser_close reads exactly one, red at none; ProxyLogTests holds the server pipe's retired event 7, red without the marker; and the real-browser idle arm stops the clock once the cap is armed, which was not watched red.
The +19 is the idle close, resume and shutdown redesign of 2026-10-03 -- P1, P2 a, P3 b, P4 b, P5 a, e1 and e2 of P7, Q324 a then c, Q326 a and Q327 a -- and the gate took two rounds. Round 1, at 22a49f2, read 922 of 923 from both shells: SaturationTests.TheDesignPointHoldsWithEveryProcessBrowserAndSessionAtOnce closed each peer's browser and expected the next call to start a new one, which is the behaviour P3 b replaced, so every peer met the refusal that names browserai_resume. Its peers now resume after their own close and then launch again, and round 2 was green. Twelve are SessionCloseTests, new: the refusal after the idle close and the resume that starts a new child; the caller's own close closing the session the same way; a close with no browser up answered and not forwarded; a close that never answers, recovered by the resume; a resume that applies nothing while a browser is up and refuses what differs; one that applies its settings with none up; a capture across a close given an archive of its own; a shutdown that asks every browser at once and is not held by one that never answers; the child's temporary folder; a real child asked both ways whether a browser is up; and the restore against a real Chromium and a real Firefox. Two are BrowserIdleTimerTests, a timer that finds only the child's own processes (Q327 a) and a headed session that is never idle-closed (Q326 a); two are ErrorCatalogueTests, the closed-session row and the unapplied-settings row, while the relaunch row's arm became row 7's and does not move the count; and one each is the startup reap in StraySweepTests, the shutdown reap in ServerRegistryReapTests and the restore options in ConfigRoundTripTests. Each of the nineteen was watched red. The in-process arms, with the rewritten ones beside them, went red against origin's product source with only the new symbols added; the startup reap against the old start-only-after-a-kill line; the real-child arm against the old count, when a close with nothing up came back as upstream's No open tabs; the shutdown reap against a teardown that forgets the reap it owes, and only once that count was fixed, because until then every teardown reaped; and the restore arm, both families, against a tree without the restore options, with its wait cut to 30 s for that run alone because it otherwise waits out the 30-minute hang detector. The rewritten real-browser idle arm was not watched red.
The +8 is the four fixes the maintainer decided on 2026-10-03 -- Q296 c, Q297 b, Q312 b and Q322 a -- and the gate took two rounds. Round 1, at 5b1a117, read 903 of 904 from both shells: StraySweepTests.TheSweepMutexIsNamedOnceInTheProductAndEveryEntryPointReachesThatName counts the places Program.cs builds a sweep, and the sweep a server started during an update now starts once the updater has gone had made a third; the two starts share one local function since, and round 2 was green. Four are the safe-mode watch, in SafeModeWatchTests: the switch read as a whole argument and nothing that merely contains it, whose process counts as the run's, what the row and the refusal say, and a real process found by a real scan through a copy of cmd.exe carrying a switch the session's own watch does not look for. The watch itself was planted red with MOZ_SAFE_MODE_RESTART=1 in the forced environment: one Firefox arm waited out three minutes and the run exited 10, naming the safe-mode content process. One is the setting, ChildEnvironmentTests.EveryChildIsStartedWithFirefoxsSafeModeKeySwitchedOff, red with the line out, and it is the second named exception to the plant-it-red rule because the behaviour behind it needs a key held down. One is the pipe, ServerPipeTests.ACallerThatConnectsAndNeverFinishesBlocksNobodyElse, red against the one-thread pipe after the whole five-minute hang detector. One is the boxes sentence, ModelSurfaceTests.TheInstructionsTellAModelToAskForBoxesBeforeACoordinateTool, red with its phrases missing. One is the still-installing row, ErrorCatalogueTests.TheStillInstallingRowIsEmittedWhileTheUpdaterRunsAndTheCallIsServedAfter, which also holds the door in process; the published arm beside it replaced Q286 b's and does not move the count, and it was red against a binary published without the change, when the tool list came back as an error. Corrected 2026-10-03 (previously "The other +26 since 870 arrived without a line here: the coordinator core and the @playwright/mcp 0.0.83 review each passed a two-shell gate, the review's at 896 from both shells as its lane reported it, and neither restamped this paragraph"). The +26 has its own paragraph below, and the coordinator core never passed a gate of its own: the review's was the first to run it.
The +26 is phase 2's coordinator core and the review of @playwright/mcp 0.0.83, and the core's first gate was the review's. The core landed on 2026-09-25 in five steps, 911d03e to 21de415, and stopped there on the maintainer's zoom-out pause with no gate run; the review was written on top of it, rebased onto its records, 73ecd5c, and gated twice. Round 1, at 74b413f, read 892 of 896 from PowerShell and 893 of 896 from Git Bash: ModelSurfaceTests.EverySessionGetsEveryCapabilityAndTheNewlyGrantedTenAreInTheSurface still counted browser_resume in the surface it had just left, PayloadTests.TheWorkedExampleStatesTheChainTheCommittedRecordsState still read the old chain in DECISIONS.md, and ReleaseScriptTests.NothingElseInTheReleaseDirectoryIsPublished, with RealInstallerTests.ThePackedReleaseNamesTheAppAsItsMainExeAndCarriesBothBinaries from PowerShell, read a shipping pack a fresh worktree does not have. The first two were the review's own and are fixed in e289d2b, the third was the worktree and not the tree, and round 2, at 5856f8d, read 896 of 896 from both shells, 3m 27.1s and 3m 25.3s, both FULL RUN, both publish-FRESH, with every capability PRESENT and both windows rows CLEAN. Six are the coordinator's pipe and its single instance (Q280 b, Q284 a), in CoordinatorTests: the name keyed like the census gate, red with one character of the key dropped; the pipe's DACL, remote flag and one instance, and twenty starts at once making one coordinator, both red with the first-instance flag off and two instances allowed, which made two coordinators; a person's start granting the foreground before it asks for the window, red with no grant; a show while the window is open raising it, red with the raise handed window 0; and the published app as a second start on a private desktop, red when every published start handed over a recheck. Seven are the per-user logon task (Q282 a): five in SignInTaskTests, of which the definition arm went red with the action's placeholder gone and with a thirty-second delay in the trigger, the hooks' arm with the hooks swapped, the refused-task arm with the failure logged as information, and the real scheduler's round trip with the delay and with a missing task reported removed; the slot oracle in InteropLayoutTests, red with two of ITaskFolder's slots swapped; and the clearance arm in SuiteCoverageTests, which names a test-pack task left behind. Five are the sign-in step (Q282 a, Q285 a), in SignInStepTests, red when it applied whatever the scan found, scanned with nothing staged, matched by file name and not by path, left nobody out, and when the published app's --sign-in never reached the step. A sixth plant, a scan that counted a process that had exited, stayed green, and the measurement it prompted removed the filter it tested. Four are a blocked server waking the coordinator (Q283 a): one in UpdateTests, red when the update lane woke nobody, and three in CoordinatorWakeTests, red when the task started past a coordinator that serves and when it started with --sign-in where --coordinate belongs, the last of them against the real scheduler with the app republished. Three are the coordinator's apply loop (Q285 a), in CoordinatorTests: a real process running from the install, waited on until it exits and then applied, red when the loop applied whatever the scan found, waited on its pipe alone or never joined the census; seventy stand-ins waited on 63 at a time, red with 64 handles beside the pipe and when a verb woke the loop without a pass; and nothing staged stopping at once, red when the loop joined the census first. One is the review's, SessionPolicyTests.TheDebuggerResumeToolIsAbsentFromTheSurfaceAndRefusedOnLiveness, red with the verdict still allow and the stamp still 0.0.82. Three of the twenty-six were not watched red, by the plant records of the steps that added them: SignInTaskTests.TheTaskIsNamedForThePackIdAndTheInstallRootsKey, SuiteCoverageTests.TheClearanceNamesATestPackTaskLeftBehind and SignInStepTests.AStagedPackageAndNothingRunningFromTheRootIsAppliedOnce. Arms that changed without moving the count: the two-roots real-installer arm holds each root's logon task too, red when the installed hook named another pack id; the gate-driver scan's Q305 a half, red against the two release drivers as they stood; CoordinatorTests.APersonsStartGrantsTheForegroundAndAsksForTheWindowAndAHiddenOneAsksToLookAgain drives the apply loop since step 5, red when the loop read what was staged only once; the review's verdict, surface and count arms, red with the row still allow, its notice arm and the first-run download figure's anchor, each red against the old value; and the rest pass the new seams through and assert nothing new.
The +17 is phase 1b, later on 2026-09-24, and the gate took three attempts, so every red is named here.
At 417409a the PowerShell half was 869 of 870 and the Git Bash half 868 of 870; at 883e347 the PowerShell
half was 869 again; at fcdb1c1 the PowerShell half was 869, then Git Bash 870 of 870 and PowerShell 870 of
870 on the same commit. Three reds were the suite's and are fixed, each watched red against a plant first:
the pipe describe arm read the description the instant the navigate's answer arrived, and a call is counted
until the handler that wrote its answer returns, so it now waits for the count to settle, red against a server
planted to hold the count two seconds; the coverage-block arm classified a commit-charge reading of its own
beside the block's, and at 75.0% of the limit the two fell either side of the band, so it now classifies the
reading the block printed, red with the row planted at 76%; and the orphan-console rig launched the server
through cmd /c start /b with nothing ordering cmd's exit before the server's look at it, so the server read
its launcher alive, and the launcher is now the test probe's launch-suspended mode, which starts the server
suspended and exits before the rig resumes it, red with a two-second linger planted after cmd's start in
both shapes. Two reds were not fixed and are not this tree's: a Firefox launch that never finished,
both in runs whose commit charge read TIGHT -- once in PasswordPromptTests, whose error was Playwright's
own 180 s initializeServer timeout, and once in FirefoxSessionTests, whose navigate failed after 3m 01.8s
with its text not kept. That is the stuck launch
provisioning and timings records, and the second is read as the
same one from its duration alone. The install hook's new
environment broadcast was tried as a cause and one experiment did not implicate it: the four
Firefox-launching classes ran eight times under a broadcast every 500 ms, 112 of 112 green.
Five are the user PATH, in UserPathTests (Q294 b): each install root putting its own folder on the PATH
and taking only that off, red with the PATH step planted out; an install and its uninstall leaving the value
byte for byte, red with an append that skipped the separator after a value ending in one; the registry store
keeping the kind and the unexpanded text, red with every write made REG_SZ; a bare name judged by the file it
finds first on the PATH, red with the bare-name branch planted out; and a Codex project entry naming the server
alone and saying what it finds, red with the absolute path written back. Two remove BrowserAI from a project
you pick (Q289 b), in ConfigurationAppTests, driven through the dialog's own dispatch with a picker that
opens nothing -- one holding the picker to its click and its owner and the removal to an entry of ours, one
holding a Codex removal to the project's own home -- both red with the verb left out of the session's dispatch.
Three are the installer lock (Q291 a), in InstallerLockTests: this run holding the lock, red with the
session hook not taking it; the decision table and the drivers' half went in with the protocol they test and
were not seen red on their own. Four read the gate or drive its script, in SuiteCoverageTests: every
driver holding the lock for its whole run, red three times per driver; every driver packing the suite's
installer before its run, red once per driver; the clearance comparing only each client's browserai entry
(Q292 a), red against the whole-file hash; and the clearance reading the user PATH byte for byte, red against
the script without that reading. One packs the suite's installer (Q287 a),
ReleaseScriptTests.ATestPackOnlyRunPacksTheSuitesInstallerAndItsTwinAndNothingElse, red against the script
without the mode, and one holds that installer to this tree's build,
RealInstallerTests.TheSuitesInstallerIsTheBuildThisRunTested, red against a 1.1.0 test pack packed for the
purpose. One is the Codex classifier,
CodexRegistrationTests.ACodexEntrySpelledWithAVariableIsNeverOursBecauseCodexExpandsNothing, red while the
classifier still expanded the variable. Arms that changed without moving the count: the real-installer
arms now hold the per-client registration record and the user PATH, red against a test pack of a planted
build; the pipe stop arm waits for each browser to exit, red with a single read planted before the server's
exit; and WindowWatchTests holds that an unwatched run fails in every mode (Q290 a), red against the old
decision.
The +12 is phase 1 of the coordinator work, later on 2026-09-24, and both halves of the gate were green on the first attempt.
Eight are the pipe each server answers on, in ServerPipeTests, and each was watched red against a
planted defect: a published server describing itself, planted red by reporting the publish directory as its
working directory; the pipe's DACL holding one entry for the current user where the default holds more, red
with an Everyone entry added; the remote-client flag read from the client's end, red with the flag removed; a second instance and a
name somebody else created first both refused, red with the first-instance flag removed and again with a
second instance allowed; a server dying mid-answer read as no answer through the length prefix, red when a
short body was accepted; a hung listener costing the whole bound while the census still says held, red at a
tenth of the bound and again with the census planted false; a free marker, a marker with no pipe and a pipe
served by another pid, red with the census skipped, with the pid check skipped and with a missing name waited
on; and a stop through the pipe ending a published server the graceful way, red when the stop exited the
process outright and left its instance directory behind. Three are the update refusal, in
UpdateInProgressTests, against the published binary: a stop with a call in flight answering that call, red
when the stop refused nothing; a server started while its install's updater runs refusing calls, starting no
child and ending with the updater, red with the updater never looked for and again with no watch on it; and
the same server with no updater serving normally, red when every server read as updating. One is the
catalogue row, ErrorCatalogueTests.TheUpdateRowIsEmittedByACallCutOffByAStopAndByACallMadeAfterIt, red
when a second answer got through the door and again when a call cut off in flight was told nothing had run.
The +10 is the focus guard and the batch around it, later on 2026-09-24, and both halves of the gate were green on the first attempt.
One is the residue scan, HouseRuleTests.NoCommentCarriesTheResidueOfTheScriptThatWroteIt, watched red
naming eight comments a patch script had written its own source into. Five are the window watch, in
WindowWatchTests: the classifier driven both ways over synthetic sightings; the row and the refusal in
all three states; the premise that this run's hooks went in; a child test host on a private desktop that
shows one window through the probe's new window-show mode and must exit 10 naming its class while the
parent never sees it, watched red with the watch's show events planted out; and the planted arm that child
runs. The watch itself was watched red against the real offender first:
RealInstallerTests.TheInstalledMainExecutableOpensOneDialogAndNoConsoleWindow, unmodified, in a host on a
private desktop, exited 10 naming its #32770, and moved onto a desktop of its own it is green, which moved
no count. One refuses a real toast in any test, HouseRuleTests.NoTestRaisesARealToast, with its
synthetic control both ways. One holds the gate's clearance to a parse of ~/.claude.json,
SuiteCoverageTests.TheClearanceSnapshotReadsTheRegistrationWithoutStartingTheClient, watched red against the
script that started the client. Two are installer facts made arms, in RealInstallerTests: two roots of one
pack id sharing one Add/Remove key that either uninstall deletes, and an installed server that never sees
the installer's variable beside a copy outside any install that does, each watched red against a planted wrong
expectation. Three arms were rewritten in place and moved no count:
ForbiddenDependencyTests.AProjectUnderSrcThatReferencesTheCodeGeneratorShipsTheMetadatasNotice replaces the
arm that refused any generated code, watched red naming three metadata packages;
InstallerHandoffTests.ThePublishedBinaryServesTheClientThatStartedItWithTheInstallersVariableSet replaces the
arm that required the deleted installer exit, watched red against a build that still carried it; and the
variable arm of ThePublishedBinaryExitsWhenItsLauncherIsGoneAndStdinIsAConsole now requires the general exit.
The +14 is Q258's Codex steps 2 to 6, later on 2026-09-24, in four groups, and both halves of the gate were green on the first attempt.
Two are the hooks, in RegistrationTests: one hook registers every client and the record carries an
entry for each, and a client the machine does not have is named in the record without failing the hook --
both planted red by a hook that registered only the first client. Six are the window and the registrar's
project path: every registration action belongs to exactly one named client, planted red by one
identifier block for both clients, which also reddened the older NoCommandCanBeMistakenForTheCloseButton
on its distinct-identifier check; removing a project registration is offered only where there is one of
ours; and four arms over McpRegistrar.ApplyToProject against the double -- the project's home and never
the user's (planted red by an empty environment), a stale entry of ours removed before it is written again
(planted red by deleting the remove-first), another install's entry refused (planted red by skipping the
gate on a remove), and the residue removed only while empty (planted red by the File.Delete it was first
written with). The re-register affordance was planted red in the renamed status arm, which is why the
rename moved no count. One is an uninstall over nothing, which now runs nothing for either client,
watched red against the code before the fix. Five drive the real Codex under a scratch CODEX_HOME:
its exit-zero dialect, the user-scope round trip through the registrar, a project registration written
into the repository and nowhere else -- the arm that found every codex mcp verb refusing a home that is
not there, which the double then modelled and was watched red on received Refused -- the CLI located as
an executable, and the update effect: a BrowserAI registered in Codex serves one call through a real
codex app-server and leaves nothing that holds an update, with a HELD control inside the arm. N16 added
assertions and no arms: the version half of the freshness reading, planted red by a doctored comparison
and then seen live refusing a binary one version behind, and the reconnect arm's wire version, planted red
by a shim that did not record it.
The +7 is Q261, later on 2026-09-24, and it is three groups. Four are the mechanism:
StaleToolListTests holds that a tools/call arriving before any tools/list is refused ONCE
with the list-changed notification and that the next call is forwarded, that a connection which
listed first is never refused and is sent no notification, and that browserai_resume and
browserai_catch_up each say when a session's record was last written by another build and stay
silent when it was not. Planted red twice: with the connection flag forced set at the handshake
four arms went red, one of them quoting the whole missing sentence; with the once-guard changed so
it fires every time, the refusal arm went red on the SECOND call, which is the wall the decision
rejected. One is the catalogue: ErrorCatalogueTests provokes the new row over three separate
connections, because the refusal is once per connection and a version that reused one would assert
the first client's remedy three times and pass. And two are the real clients:
ClientReconnectTests drives the real Claude Code against the published binary with its server
made to exit mid-session -- the refusal reaches the model byte for byte and the retry goes through --
and the real Codex CLI, which lists before it calls and therefore never meets the refusal at all.
Planted red against the product, by forcing the flag set: the Claude Code arm went red on the
notification count. Both skip loudly when their binary is absent, and
SuiteCapability.CodexCommandLine is new for the second of them.
The +3 is T7, later on 2026-09-24: the reap at session close. One arm drives the whole thing:
ServerRegistryReapTests opens two sessions with a real Chromium each through the published binary,
plants a thousand dead descriptors in a scratch registry, destroys one session, finds the reaper's pid and
creation time in the machine's process log, waits for that process to exit, and then holds that every dead
descriptor is gone, that the closed session's own went with its browser, and that the live browser's still
stands. Planted red: with the teardown's reap made unreachable and the slice republished, it failed on
the close should have recorded the reaper it started -- 1 failed, 1 succeeded -- and green 2 of 2 with the
call restored. A second arm holds the script -- upstream's serverRegistry.list() against the module it
was given, forward slashes, an escaped apostrophe, and no cli-client anywhere in it -- and a third, in
ChildEnvironmentTests, holds the one forwarded test hook both ways: inherited when this process carries
PWTEST_SERVER_REGISTRY, absent when it does not, refused to nobody.
And one assertion was removed the same day, with no count behind it, because it was a promptness claim in
disguise: the reaper was still running when the destroy answered holds only while the test host observes
the answer inside the reap's own window, and at full parallelism this host is starved for seconds -- it
passed alone and failed in the suite with the product behaving identically. What survives is the ordering
read out of the log. The same gate surfaced a stray the suite had been handing the sweep for as long as
that file existed: PasswordPromptTests took a session's guard and released it on the spot, so the
browser it launched into that profile was a stray by the product's own definition, and the sweep's record
names the pid the arm's own Playwright log carried. The guard is held for the child's life now, planted
red and watched in both directions under one real --sweep a second: 3 of 3 red with it dropped, 3 of 3
green with it held.
browserai_init. The fix is one line in each of the two client stand-ins,
and it is recorded because the shape is worth knowing: a mechanism aimed at a client's habit will
find every place the suite did not share that habit.
The +19 is one session's worth, 2026-09-23 into 2026-09-24, and it is four groups. Eight
are Codex: CodexRegistrationTests holds the command shapes and the -- separator, the project
scope through the CODEX_HOME lever, the four-place discovery order with a refusal that names
every place it looked, the idempotence answers that differ between the two clients, a foreign
entry neither intent may touch, and both clients listed in a stable order with distinct keys.
Three are the password prompt: PasswordPromptTests opens no window on a sign-in POST and
proves the probe can see one, and the Firefox preference reaches the child. Four are the never-
again rules the no-trace-of-AI directive needed: HouseRuleTests.NoMaintainedProseCarriesATell
over commentary and over the product's own strings,
.NoTextFileCarriesACharacterAPersonDoesNotType widened to five classes over every non-binary
file, ChangelogTests.NoEntrysDetailOpensByRestatingItsHeadline, and
SuiteCoverageTests.EveryGateDriverDeclaresTheDriveLetterSpellingItForces, which found a defect
in the drivers on its first run and a defect in itself in the same breath. And four are the
triage: the assumed-justification count held at zero, no project under src/ referencing the
code generator, every review entry adjudicating every golden snapshot by name, and a burst of
child notifications reaching the caller in the order the child wrote it.
UpdateTests.ACheckThatNeverAnswersEndsOnItsOwnBudgetAndSaysSo divided the product's
four update budgets by 4,500, which puts the outer bound at 600 ms -- below the scheduling noise
of this machine running the whole suite, so it passed every filtered run and reported
1,054 ms against 600 ms under load, with every other assertion in it green. The factor is 450
now and the arm was watched red at it. A hang detector whose bound is smaller than the jitter is
a promptness test wearing the right words, which is the failure the house rule about duration
assertions is written against and the one thing that rule's scan cannot see.
The +1 is the full-packages-only arm, 2026-09-22. ReleaseScriptTests.EveryReleasePacksFullPackagesOnlyAndTheFeedCarriesNoDeltaRow holds both halves of a decision of record: that build/New-Release.ps1 passes --delta None, which a scan can see, and that the argument means what the script assumes, which only vpk can say. It packs a real 162 KB executable twice into a feed that already holds the previous full package. The positive control is what makes it worth anything -- the same two packs without the argument do produce a delta, so "no delta" is the option working, not vpk having nothing to compare against. It was watched red on the script scan before the argument went in.
The +2 is the event-id scan and the relaunch-note guard, 2026-09-22. ProxyLogTests.EveryLogEventIdIsUniqueInItsClassAndNoRetiredIdIsInUse reads every [LoggerMessage] under src/ as text and refuses a repeated id inside one class, or any id a class's own RETIRED-EVENT-IDS: marker names. It was written for a collision found by reading and it found a second one by running: on its first run against the real tree it reported ClientLivenessLog declaring id 76 twice, which nobody had noticed. Both events left that id and 76 is retired (previously "the later of the two events moved to 77", which is what the batch did before the maintainer answered Q226 c): a log from any v1.0.0 binary carries 76 for two events, and retiring it ends that ambiguity at v1.0.0 instead of carrying it into every release after. DeadChildTests.TheRelaunchNoteDoesNotPromiseThatStoredStateSurvived is a guard on the TEXT of SessionManager.ChildWasRelaunched, not on the constant, which is the point: every other arm in that file asserts against the constant and stayed green through a sentence that claimed cookies and stored state survived a relaunch when nine measured runs say they may not. Both were watched red -- the first against the real tree, the second against the shipped string -- and one further test was WIDENED and not added: RealInstallerTests.TheSuitesPackAndTheShippingPackDifferOnlyWhereTheIdAppears went red on the first gate run after Velopack 1.2.158, because upstream now names the embedded stub from the pack title, and it took two narrowings with controls in five directions before it was green again.
The +2 is the onboarding guard, 2026-09-22. HouseRuleTests.EveryScratchClientConfigurationIsSeededAsOnboardedBeforeTheClientRuns refuses a site that hands the real claude.exe a scratch CLAUDE_CONFIG_DIR without first seeding hasCompletedOnboarding into it, and RegistrationTests.TheScratchConfigurationIsSeededWithWhatTheClientReadsAsOnboarded holds that the file exists and carries the marker before the scope is anything a child could inherit. Both were planted red: the scan named all three sites verbatim -- two in RealInstallerTests and RegistrationTests' own factory -- and the second arm failed on File.Exists(seeded). The guard is asserted and never measured against the flow, because measuring it means running the sign-in flow on the maintainer's desktop, which is the event being guarded against; its hazard row is open saying so. And planting it found a blind spot in a neighbour: wrapping the registration factory across two lines made HouseRuleTests.EveryArmInAFileThatOverridesTheEnvironmentRunsBesideNothing stop seeing that file at all, its tree count falling 2 → 1 and the arm going red on its own non-vacuity floor -- which is the only reason it was noticed. The factory went back on one line; the scan was not widened and the floor was not lowered, and the line-based limit is now stated on it by addition.
The +4 before that are the four arms holding the session-lifetime rules on the published binary's own wire, earlier the same day -- who destroys a session, what a destroy takes, where an uploadable file has to live, and that a session moves by hand. Each was planted and watched red first: 18 of the 19 required phrases named as absent, and the other two present already, which is the positive control that the scan can find a phrase that is there. The +1 before that is the arm holding the charter version-chain example to the payload lock and the committed browsers snapshot, earlier the same day.
ThirdPartyNoticeTests.TheReadmeTablesNameEveryPackageThatShipsAndEveryFamilyThatIsProvisioned
holds the two licensing tables under
Third-party components to the sources the arm below
already holds the shipped notices to, plus the committed
upstream-snapshots/browsers.json for the revision -- which the build
regenerates from the resolved payload, so all three sources are committed and
this one runs whole on a clean clone. It was watched red on four offences at
once, which is what the same correction left behind one document across:
"the payload ships 'playwright' and README.md's third-party components section
does not name it", "'firefox' is a provisioned family and README.md's 'What
the user's machine downloads' table has no row of its own naming it",
"README.md's 'What the user's machine downloads' table says 'chromium 1237'
and the committed browsers.json snapshot says 1245", and a fourth saying no
revision stood beside firefox at all. The positive control is inside the
arm, not beside it: each provisioned family must state a revision of
its own, so a pattern that has stopped matching -- or a previously "..." cut
that swallowed a live cell -- is red, not quietly empty.
The +2 is the two omissions nothing could see, 2026-09-18.
ReleaseScriptTests.APayloadWithNoOverrideInForceSaysSoRatherThanSayingNothing
is the control on the manifest's new pulledForward key -- watched red at
"Expected to contain "pulledForward": null", and it differs from the
populated arm in exactly one file, so a field derived from the payload lock
instead of the payload manifest goes red there.
ThirdPartyNoticeTests.TheNoticesNameEveryPackageThatShipsAndEveryFamilyThatIsProvisioned
is the one worth naming: it enumerates build/payload/package-lock.json and
ProvisionedBrowsers.Families, not a typed list, because a typed list
can only be wrong in the direction of naming a path that is not there -- it
cannot notice a package that ships and is in nobody's list, which is exactly
what playwright had been doing since the first payload build. It was watched
red naming both omissions and the three paths under them, and its first
shape was wrong in a way the red showed: a plain Contains passed on the
very omission it was written for, because playwright is a substring of both
@playwright/mcp and playwright-core. The existing eight-file manifest arm
and three rows in Obligations moved with them and are not counted here,
because neither is a new test case.
The +1 is a read-only file, 2026-09-17.
TreeDeleteTests.AReadOnlyFileIsRemovedRatherThanReportedAsANodeThatWouldNotGo,
planted red and watched at Assert.That(File.Exists(loose)).IsFalse() --
"Expected to be false but found True" -- against the shape that found the
defect: a read-only git loose object under two ordinary directories, a
read-only directory beside it, and a held file in the same tree as the
control, so that clearing an attribute cannot become swallowing a sharing
violation. It was not an idea: this release's own gate went red at the head
of run 1 on a tree nobody had changed, and the defect was in
Runtime/TreeDelete, not in the rig that provoked it.
The +6 is the dead browser server, in both directions, 2026-09-17. Four arms
in DeadChildTests and two in ErrorCatalogueTests, each planted red before the
change that makes it pass. ACallForwardedAfterTheChildDiedComesBackRatherThanWaitingForever
is the one worth naming: it was watched red at 5 m 00.924 s, the whole of
TestDefaults.InProcessHang, against a forward handed to a child whose transport
had already closed -- "No frame arrived on this pipe in 5 minutes while waiting
for the answer to 'tools/call' (id 3)" -- and it answers in 2.1 s with the
door check in. AResumeRelaunchesAChildThatHasDiedAndSaysSo was red on the
relaunch sentence being absent from an answer that said nothing had changed. The
other two are the controls, and they are the reason the first two cannot be
satisfied cheaply: a resume of a healthy session still changes nothing, and a
call held open on a healthy child still gets the child's own answer, so a
liveness question answered too readily is a red build, not a refusal on
every slow page action. The two catalogue arms provoke the two new refusals
through real conditions, which is what stops a sentence nobody can reach being
written.
The +5 before that is the dated playwright-core override and the config key it was
taken for, 2026-09-17. Four arms in PayloadTests and one in
ConfigRoundTripTests, each planted red before the change that makes it
pass. TheDatedPlaywrightCoreOverrideIsStillNeeded,
TheExpiryComparisonFiresInBothDirections and
TheResolvedPlaywrightCoreIsWhateverTheOverrideSays were deleted when the
override was retired: @playwright/mcp 0.0.82 shipped a playwright-core
carrying the fix, the overrides block went, and deleting a test for a
mechanism that no longer exists is not a skip. The property they guarded came
back to where it was before 2026-09-17 --
TheLockRecordsUpstreamsOwnExactPinOfPlaywrightCore asserts the resolved
playwright-core equals the declared one again, planted red against a
doctored lock, and that is also what catches an override being added back. The
fifth was renamed and not deleted, because 0.0.82's own config.d.ts
declares filePaths and the old name asserted otherwise; it is
ConfigRoundTripTests.TheChildHonoursFilePathsAndHandsBackAbsoluteWhereUpstreamDefaultsToRelative
now, measuring the same running child for the same reason -- loadConfig
validates nothing, so a declaration in the typings was never the evidence. TheDatedPlaywrightCoreOverrideIsStillNeeded is the exit: it reads the
committed lock, so it runs from a clean clone, and it goes red the day
@playwright/mcp pins a playwright-core at or above the override, naming
the file and the key to delete. It was watched red against a lock doctored so
the wrapper already pinned it, with
TheAssembledManifestDeclaresWhatTheCommittedLockRecords going red beside it
on the same doctoring -- which is the arm that catches a payload built from
something other than the committed record.
TheExpiryComparisonFiresInBothDirections is its positive control, because
the exit asserts on every ordinary day that nothing happened: it drives nine
orderings through the same comparison, including a release outranking every
prerelease of its own triple, and requires a shape the comparison cannot order
to throw and not answer. TheResolvedPlaywrightCoreIsWhateverTheOverrideSays
holds the manifest and the lock together, because npm writes no overrides
block into the lock it produces and the manifest is therefore the only record
that one is in force. And
ConfigRoundTripTests.TheChildHonoursFilePathsEvenThoughItsOwnTypingsDoNotDeclareIt
is the measurement the typings cannot give: filePaths is the one generated
key @playwright/mcp's own config.d.ts does not declare, so a running child
saying the value back is the whole of the evidence. Planted red and reporting
an empty string where absolute belongs.
The +3 before that was three checks, earlier the same day, one per rule that
batch turned from a habit into a mechanism. Change by change, each named with what it was watched
red against:
HouseRuleTests.NothingAsksAVolumeHowMuchRoomItHas, a tree-wide scan that named
four files before the free-space check was removed and none after
("Expected to be empty but received src\BrowserAI\Sessions\SessionEnvironment.cs: asks a volume for its free space (AvailableFreeSpace) ..."),
with controls in both directions -- three real spellings caught, and
DriveInfo.GetDrives, which enumerates mounted volumes and says nothing about
free space, deliberately not. A second red was planted for the same change and
deliberately did not survive it: the literal inversion of the arm that used to
assert the refusal, a volume reporting 12 MiB free asserted not to refuse
("Expected to not be equal to True but received True"). Its seam is
SessionEnvironment.FreeBytesOn, which is part of what was removed, so there is
no condition left for it to arrange -- said here and not quietly dropped.
ChangelogTests.NothingThatReachesAReleaseBodyCarriesACharacterAPersonWouldNotType,
red against the 1.0.0 preamble
("an em dash (U+2014); write two hyphens, or a comma, or two sentences"), which
is the character half of the maintainer's no trace of AI directive; and
ChangelogTests.ALegendThatIsNotATableRefusesTheBody, red against a generator
that accepted a one-paragraph legend and flattened it
("Expected to contain table"), with the accepted fixture beside it as the
control. Eleven arms of ChangelogTests went red together on the way to
that last one, which is what a shape change looks like when the shape is read in
four places.
The +6 before that is five checks the previous batch added and one the
2026-09-16 re-measurement added without the count being re-stamped. Change by
change, each named with what it was watched red against:
ReleaseScriptTests.TheSecondFeedIsClearedBeforeItIsPackedIntoAndNothingElseIs,
red three ways -- with no script at all ("Expected to be 0 but found 64"),
with a script that finds the files and deletes none ("Expected to be equal to
"" ... but received "BrowserAI.app.test-1.0.1-alpha.0.19-full.nupkg, ...""),
and with one that reaches a directory up ("releases.win.json is not the test
pack's regenerated output and nothing may delete it");
JobContainmentTests.AWalkedPidThatVanishedBeforeTheQueryIsExitedAndAnyOtherFailureIsStillAFailure,
red at the classification level against a stub carrying the old behaviour
("Expected to be equal to Exited but received Unreadable"), with the two
Win32 error numbers measured and not quoted and both host branches watched
red separately; NeverByImageNameTests.TheScanReadsTheFilterRatherThanTheApi,
whose first run over the real tree returned four offenders nobody had ever
called a violation -- two release scripts and three test files, caught by
WHERE matching Where-Object -- which is the control working in the direction a
narrowing most needs one;
FirstRunCacheTests.TheDownloadFigureInTheCoverageLineComesFromTheConstant, red
at "Expected to contain \"207.3 MB\" ... but received \"downloaded 203.8 MB from the CDN because some reason\"" against the literal every run of this suite
had been printing for a day;
RecordedCountTests.TheUpstreamVariableCountInTheDocCommentIsWhatRowSeventeenSays,
red at "Expected to be equal to 45 but received 43" against two records
that had each been correct about a different version of upstream. And the sixth
is ProvisioningTests.TheQuotedFirstRunDownloadSizeIsTheFigureTheKnowledgeBasePublishes
from 2026-09-16, which landed with its own red and whose arrival never reached
this paragraph -- recorded here and not absorbed, because a count that moves
without a sentence is the thing this paragraph exists to prevent.
The +9 before that is the orphan first run of the published v1.0.0 and the three checks that could not fail, 2026-09-15. Change by change, each named with what it was watched red against: DirectStdioServerTransportTests.DisposingDoesNotWaitForAReadTheCallerWillNeverEnd, red at 5 m 00 s -- the whole of TestDefaults.InProcessHang -- against a transport whose DisposeAsync awaited a read loop parked on a stream that never returns; InstallerHandoffTests.ThePublishedBinaryExitsWhenItsLauncherIsGoneAndStdinIsAConsole, two arms, red at 10 m 00 s on the variable-less one -- the whole of TestDefaults.ProcessHang -- against the shipped binary started with a launcher already gone and a console stdin; InstallerHandoffTests.ARunWithNobodyToServeStartsNothingAndCreatesNothingButItsLog, red in 650 ms against the same binary, naming the playwright-mcp child, the stray sweep and the update check it had already run; ReleaseScriptTests.APublishLogWithNoIlcPassIsRefusedAndOneWithAPassIsAccepted and .TheReleaseScriptRemovesTheIlcIntermediatesAndThenRequiresAFullPass, whose subject is a release check that could not fail -- the refusal was watched live against a real incremental publish log before the arms existed; .TheSuitesInstallerIsPackedUnderATestIdIntoADirectoryOfItsOwn and RealInstallerTests.TheSuitesPackAndTheShippingPackDifferOnlyWhereTheIdAppears, which are the two halves of the installer arm no longer installing under the shipping pack id -- the identity comparison carries a synthetic both-directions control, because a real pair that happens to agree is indistinguishable from a comparison that stopped looking; and ReleaseScriptTests.ARealInstallIsNeverDanglingAndTheSuitesOwnLeftoversAlwaysAre, over constructed inputs, not a planted registry key, because planting one would be the suite doing the thing the refusal exists to prevent. Setup.exe twice over one install root. The +1 is the scan that closes the suite race the packed release let loose -- HouseRuleTests.EveryArmInAFileThatOverridesTheEnvironmentRunsBesideNothing, holding that every arm in a file that constructs an EnvironmentScope carries the keyless [NotInParallel], not a key, and watched red twice against this tree: once with the keyed attribute the race was measured through, and once with RegistrationTests' class attribute removed, where it named all twenty of its arms. .nupkg -- and they are the gate working, not a rule being broken: this machine's Releases/ holds artefacts from before the pack id and the download names changed, so both capabilities read ABSENT..."). A release was packed from this tree's own layout, so Releases/ now holds BrowserAI.app-1.0.0-full.nupkg and BrowserAI.exe, both capabilities read PRESENT and both arms run. That is the same gate reporting the same thing about a different machine state, which is why the release checklist packs before it runs the gate -- and it is also how the race was found: the installer arm had never once executed inside a full suite before 2026-09-15. The +1 before that was the coverage block learning to state the publish freshness the suite had been establishing in silence, and the red was watched four times, one per direction the row can be wrong in. PublishedSlice.EnsureFresh compares the published NativeAOT binary against every input that goes into it and has done since the beginning; what it did on success was nothing, and the block's published slice row says PRESENT, which is a claim about existence. So twelve green gate logs held no sentence about whether the binary they drove belonged to the tree they were reading -- and on 2026-08-30 a runner with a staleness suspicion reached for the nearest figure to hand, a commit date, putting 56383c9's 01:20:40 touching src/BrowserAI/Sessions/SessionLock.cs beside the binary's 01:14:16.500 and reporting four gate sets -- twelve full runs -- as having driven a stale binary while passing the check. Every reading in that account was true and the conclusion was false: that file's own timestamp was 01:12:22.665, one minute 53.8 seconds before the publish, and git commit records when it ran instead of touching a working-tree file. Dissolving it took an investigation that one printed line would have ended, and the line now exists: publish freshness FRESH exe 2026-08-30T02:03:58.876Z is 2h51m36s newer than the newest of 95 inputs (src\BrowserAI\Sessions\SessionLock.cs, 2026-08-29T23:12:22.665Z). Both timestamps are UTC to the millisecond, and both halves of that are the incident: the gap that settled it was 113.8 seconds, so a row printed to the minute would have made things worse, and the two figures compared that day were a local-time file stamp and a commit date with no zone named in either. The row and the refusal are one comparison, not two -- Measure walks the inputs once, RefusalFor renders that reading as the exception and RowFor renders it as the row -- which is asserted live in both directions and not left to the construction, because the construction is the whole of the guarantee and a second enumeration is exactly how the corpus scan came to disagree with git ls-files by 520 files under a remark saying the two matched. The four reds are the four ways the row can lie: the row absent from the block, where both this arm and the older block test named the missing title; a stale reading rendered without its warning; a fresh reading rendered with one; and the guard's own refusal decoupled from the verdict the row prints. The STALE rendering is driven from synthetic readings and never from the tree, because a healthy tree publishes and then runs, so the branch that matters would otherwise first execute on a day somebody is already confused -- and arranging a genuinely stale publish inside a test would leave the tree needing a re-publish to go green again. No hazard row was added, on this file's own precedent: a row records a blind spot and a test closes it. The +1 before that is the hazard index's parser learning to say when it has dropped a line, and it was watched red on the shape that provoked it. HazardIndex.Rows keeps a pipe-leading line only when it splits into exactly eight fields and discards every other one in silence -- which is right for the header, for the separator and for the three-column table above the index explaining what each column is for, and catastrophic for a row somebody wrote a bare | into. On 2026-08-30 FileShare.ReadWrite | FileShare.Delete, written into an evidence cell to record what a fix had opened a file with, split its line into nine fields and deleted the row the same commit had just re-opened. A dropped line leaves the open tally and the closed tally in the same instant, which is the one failure this table's counting mechanism cannot describe: RecordedCountTests can see that a number moved and never which row went missing. The red established that it is worse than that. With the guard stashed and the offending row planted, all nine tests of the two classes that read this table passed -- the tally included -- because a row that arrives malformed never registers in either direction, so no number moves at all and there is nothing to disagree with. With the guard in, the same file fails naming HAZARDS.md:299, the field count, the leading cell and the three ways out. The escape it recommends had to be made true and not suggested: \| is GitHub-Flavoured Markdown's own literal pipe, and a parser that went on splitting at it would have moved the row from one silent skip to another -- so HazardIndex.SplitRow honours it, and the second red is the same planted row written that way, which parses into eight fields and is counted, taking the tally to 46 open against a published 45. The guard is a second walk of the table, not a second reading of it. Rows scans the whole file and keeps what looks like a row; HazardIndex.TableLines walks the contiguous run of pipe-leading lines below the header; they share the split and nothing else, and the two counts are asserted equal -- which is the half that catches a skip the field count cannot see, such as a row whose Area cell is all dashes, or one written with a leading space that ends the region early and takes every row below it out of the guard's sight. No hazard row was added, and that is this file's own precedent, not an omission: a row records a blind spot, and a test closes it. The +3 is the failure dump learning to read the files it exists to inline, and every red was watched against the real body and not planted around it. The dump LauncherWait.Evidence produces is the whole account of a containment failure, because the scratch tree it names is deleted when the test unwinds -- and on the 2026-08-30 release gate's sixth run, the Firefox stall the stderr tee had been armed for two hours earlier, it printed (unreadable: ... used by another process) for all three capture files and (0 bytes) beside each. The reason was the reader, and it was the reader's own share mode. File.ReadAllText asks FileShare.Read; Windows checks a reader's share mode against the accesses a live writer already holds, so a reader that does not permit writing is refused however permissive the writer was -- node's fs.openSync shares read, write and delete precisely so a log can be tailed, and lost the argument anyway. It opens FileShare.ReadWrite | FileShare.Delete now. The red reproduced the phantom as well as the refusal, which was not the expectation: written to assert only that a printed byte count had been measured, it came back --- cli-stderr.log (0 bytes) --- against 63 bytes written and flushed -- character for character the line the gate had printed -- so FileInfo.Length, the size the enumeration cached, was wrong and not merely unmeasured. The number comes off the open stream now, and a file nothing could open carries no number at all: the second arm holds an exclusively-held file, where (unreadable: ...) is the correct answer and must stay one, and requires no byte count beside it. That arm is what the fix cannot satisfy by accident, and the first is its control -- if lengths ever stopped being printed, that one goes red. The third is the other half of the pair and is deliberately described as the smaller half. The driver closes its cli-stderr.log tee once the child it was teeing ends, on 'close', not 'exit' because 'exit' fires while stderr may still have chunks to deliver and the tidy-up would drop the last thing upstream said. It was watched red at 10 m 00.4 s, the whole of the arm's hang detector, reporting "the driver was still holding ... with the child it was teeing long gone" -- and by hand outside the suite first, where the held file's enumerated length also read 0, against 61 real bytes. TerminateProcess, where no handler of any kind runs, and a browser that stalls forever never ends its child -- so on the failure actually worth diagnosing the file is still open when the dump reads it. That is why the reader had to be the fix and the close is the tidy-up, and it is why the arm asserts the driver is still alive when the file goes free: a driver that died would release the handle too, and a green built on that would be saying nothing. The +3 before that is the release-preparation batch, and each was watched red before its fix went in. The first is the session guard's record: a process re-taking a directory it itself last held was logged "previous holder was PID n, still running: True" -- true word by word, and read by anyone scanning the log as a live stranger being evicted, which is the one event on that path worth waking up for and had happened five times in eight thousand. Counted 2026-08-30 over the machine-wide process log, predicate quoted: since the 2026-08-26 logging cutover that file holds 8,423 Session lock reclaimed lines, 8,418 of them carrying still running: True, and zero Session lock acquired lines -- in the two 2026-08-29 files alone it is 2,081 of 2,081 -- because destroy and set_purpose both dispose the live session and re-acquire, and are the only acquisitions that reach that file at all. AProcessReTakingItsOwnGuardSaysSoRatherThanReportingAReclaim was watched red with the branch removed, coming back with event id 2 where 12 is required; its negative arm is the load-bearing half, refusing the old sentence anywhere in the capture, because asserting only that the new record is present would pass just as well if a well-meant "log both" left the false one exactly where it was. The pinning test one method above it -- Reclaimed and HolderRunning: true for exactly this shape -- is unmodified and still green, because both are answers about the directory, not about who is asking and both are still right. The second is build/payload/package-lock.json joining the published slice's freshness inputs: a payload re-resolve left every slice arm driving a published tree carrying the old one and reading as fresh, and the red needed no plant on the day it landed -- the publish was seven hours older than the lock, so the check fired unprompted and named it. The counterfactual was planted anyway, because the interesting claim is that the walk cannot see the file at all: it prunes every directory called payload, so the corpus arm goes red with the entry removed however the enumeration is widened. The third is the coverage block's commit charge row, which exists because the hazard index's six-run-gate row closed on 2026-08-24 by naming the commit charge beside the run as the one reading that separates its cause from a live one -- and then nothing took it, so for six days no gate could ask that row's own question. Red watched with the row removed from the block. Its bands are exercised from synthetic readings and never from the machine, which is MachineLoad's standing rule: a bound on a live commit figure is a test that passes or fails depending on the developer's other windows, and a healthy machine sits in one band for ever, so the two bands that matter would otherwise first run on the day something is already wrong. A fourth change moved no test and was watched red in both directions anyway. The browser-containment driver spawned cli.js with all three streams piped and read only stdout, so upstream's account of every launch that did not happen went into a pipe nobody drained -- which is why the 2026-08-29 stall could be named and not explained. It is teed to cli-stderr.log in the scratch directory the failure dump already walks: provoked into failure, the dump now carries cli-stderr.log (60 bytes) with upstream's own sentence beside the child-stderr.log (0 bytes) and child-stdout.log (0 bytes) that were all it could say before, and with the tee removed the file is absent altogether. No test asserts it, and that is deliberate, not an omission: what a browser writes to stderr is upstream's business and changes between revisions, so it is evidence for a reader and never an assertion. The +1 before that is the harness's process-log reader, which could answer with a stranger's records. ProcessLogRecords selected a writer by matching pid=<n>@ -- the pid alone, with the creation FILETIME behind the @ read past and never compared -- while the type's own remarks said in as many words that a bare pid does not identify a writer. The machine-wide log is kept for thirty days and Windows reuses pids well inside that window, so the scope was answerable by whoever last wore the number: demonstrated live on 2026-08-29, a read scoped to the running test host's own pid came back holding records written on 2026-08-24 by a different process wearing it. The reader takes (pid, creationFileTime) now and matches both halves, the pid-only entry point is gone and not caveated, and neither caller had to be taught anything -- both already knew the identity they were asking about. The red was planted and not watched live, and that is a property of the subject, not a shortcut: whether this machine's log happens to hold a stranger wearing this run's pid depends on the box and on the last thirty days, so a live arm would pass by matching nothing on most machines and go red only by luck. ARecordWearingThisPidWithAnotherCreationTimeIsAStrangersAndIsNotReturned hands the reader a directory holding three records that differ in nothing but the FILETIME, and with the old marker restored it returned all three -- ours, the stranger's, and one whose FILETIME merely begins with ours, which is the case the marker's trailing separator exists for and which a prefix match would have collected in silence. The three planted lines go through the same WriterHeader expression the file's live arms use, so the control cannot outlive the record format it is written against. The -1 before that is a deletion, and a deletion carries no planted red. browserai-sessions.json -- the per-root roll-up, written to the parent of the directory the caller named and read by nothing -- is gone, and the two tests that were about the file went with it: the one that blocked the write with a directory and required the answer to say so, and the half of the other that parsed the file back to prove it covered one root only. One of the two came back narrower instead of dying, which is why this is -1 and not -2. The rest of TheRollUpCoversOnlyTheRootInPlay asserted the sibling-sessions line in the init answer -- which survives the deletion, and which that test was the only assertion in the tree over -- so it is TheSiblingSessionsLineCoversOnlyTheRootInPlay now, and it is stronger than the sentences it replaces: the two roots are populated in an order that makes each DoesNotContain name a session that already existed when the answer was written, which the old ordering did not. The one property that genuinely died with the file says so in its own remark: a roll-up sat at a session's own root and was never propagated to an ancestor, which is a fact about which file got rewritten, and the walk behind the surviving line is a path prefix that does see what is nested below it. The +3 is the harness's own reclaim pass, which could terminate a live run's processes and never said so. The record that names a killed run's leftovers carried a subject and no owner, so a second harness process reading a live run's file ended that run's browsers, probes and slices with exit code 1 and then deleted the tree they were using -- 18 of 18, and the mechanism behind an exit code chased for eleven days as a crash. A row now names the process that started it and holds the job containing it, and the pass acts only on rows whose owner is neither this process nor any process still running; rows it declines are written back instead of the file being emptied, because sparing the process and blanking the record would take the recovery with it. Each of the three was watched red, and one of them twice: the kill with the owner gate removed, reporting the measured line verbatim about an owner that was still running; the recovery with the check inverted, against an owner that was really started and really killed; and the new WARN the pass writes to the machine's process log -- the pid, the owner, the record it honoured and the exit code read from the constant the call hands it -- watched red both suppressed and firing on a pass that ended nothing. The +1 before that is a diagnostic for the one ceiling this suite could name and never read. A desktop heap spent to the byte kills a Chromium before it creates a single window, silently -- reproduced deliberately over eighty launches -- and GetPerformanceInfo cannot see it, because UOI_HEAPSIZE reports a desktop heap's size and no API reports its usage. So the attribution failure now tries the allocation instead of asking about it: one CreateWindowExW with a 2,048-character title, on the same desktop, at the instant the browser is found gone, destroyed again immediately, with the verdict in the message. The 2,048 is the measured regime, not a round number -- window text lives in that heap, so the title length is the allocation, and a probe asking for the smallest one would report a clean create on a desktop already killing browsers. A refusal that sets no last error at all is the signature and the message says so, which is the one reading that would otherwise be thrown away as an unread error. StraySweepTests.ABrowserGoneBeforeItsWindowAppearedIsAskedWhetherTheDesktopHeapWasSpent was watched red before the probe was wired in -- the message carried the exit code, the streams, the log and the machine and said nothing whatever about a window -- and it provokes the death with the test probe run with no arguments at all, so the branch is deterministic and needs neither a browser nor a rig. What it asserts is that the reading was taken, never what it says: the verdict is a property of the developer's other windows, which is the bar MachineLoad is already held to, so it requires the heading and exactly one of the five verdicts. The +3 before that is the three behaviour changes the maintainer released, each watched red first and each the option the review called the honest one. A server start opened the SQLite store of every session on the machine, leaving a -shm and a -wal beside each -- the sweep goes probe-first now, and the red is the review's own measured shape at the mechanism: a cleanly-closed session's directory at two files, not four. An autonomous idle browser close left no trace in the only record there is, under a heading that tells its reader "this is what BrowserAI did" -- it writes an in-flight row before the call reaches the child and settles it from the child's answer, and the red was the settle timing out against a row that did not exist. And the short-circuit in front of the verdict door was a prefix test, so browserai_zzz was refused by the session manager and recorded nowhere while an unjudged upstream name was recorded on the session it named -- it is an exact match now, which also makes the answer rows of tool-verdicts.json load-bearing at run time, not build-time only. Two residuals are asserted and not described: a browserai.data that will not parse reads as Session at guard depth (kept either way, so the sweep is unchanged), and a call with no resolvable session still writes no row, because there is no directory to write one into. The +11 before that is the post-course-correction re-review, and ten of the eleven were watched red before the fix went in. Six are model-facing answers that were wrong, not merely thin: a purpose written by one agent carried invisible TAG-block text into another's context, because the sanitiser iterated char and char.GetUnicodeCategory never answers Format for half of a supplementary-plane character; browserai_catch_up served page=4294967297 as "page 1 of 1" with no error, an unchecked truncation that also made the mirror case quote a number the caller never sent; a refusal named U+0007 in words and then carried the byte twice into the model reading it; another leaked the C# identifier (Parameter 'canonical') into the one sentence a model is supposed to act on; browserai_list on an unmounted drive letter answered "No BrowserAI sessions under 'Q:'" confidently and in 1 ms; and a session directory deep enough to leave no room for browserai.data was accepted, created and locked before failing with a message about the browser. That last red found a longer suffix than the review had named -- SQLite composes browserai.data-shm from the store's path and its Win32 VFS is MAX_PATH-bound too, so the store failed before the child's working directory ever did, and the budget is derived from the measurement, not from the report. Two more are refusals that were reaching a person, not a caller: a tool name written twice inside one half of tool-verdicts.json exited the process with a bare dictionary exception naming neither the file nor the row -- planted in the text, because a JsonObject cannot hold two properties under one name, which is part of why it went unnoticed -- and the five PLAYWRIGHT_MCP_* variables that override a generated config key, ALLOW_UNRESTRICTED_FILE_ACCESS first among them, were absent from a child's block by construction and absent from ChildEnvironment.Refused by omission. Two are the release manifest gaining the field that can express a crunch override, which two documents had been claiming it already had. The eleventh had no red and says so: CanonicalPathTests.TheAncestorWalkGivesUpExactlyPastItsLimitAndSaysSoWhileStillAnsweringAPath is the control PathVerdict.Unestablished has never had -- reachable by depth alone, with a successful create, against a record that called it unreachable -- and the behaviour it asserts was already correct, so there was nothing to plant. Four tests changed shape without moving the count: ReVerificationIndexTests.Exists was harmonised with HazardIndexTests.Missing on the three axes where it had silently been narrower, with the fourth axis left different and the reason written into the method; ChildEnvironmentTests and ToolVerdictTests each gained a denominator; and AppendOnlyRecordTests seals a fifth review. The +2 is the reconciliation pass, and both are mechanisms, not behaviours. HouseRuleTests.TheSessionGuardsTwoLoadBearingLiteralsAreTheOnesItIsWrittenWith reads LockFile.cs as text and holds the two literals the session guard is made of -- Hold's FileShare.Read and the probe's FileAccess.ReadWrite. Both were watched red on the real file, one perturbation at a time, and the point of the rule is that neither perturbation reddens anything else: sharing writes and probing read-only both leave a file that opens, so every behavioural arm in the suite stays green while one BrowserAI drives another's profile or every driven session reports as free. StraySweepTests.OnlyTheSuiteEverWaitsForTheSweepGate is the second, and it arrived with a fix, not on its own: an arm that needed its own sweep pass to have RUN was losing the machine-wide gate to a real BrowserAI another test had just started, once in five full runs. The flake was reproduced deterministically -- hold Global\BrowserAI-Sweep from another process and the arm fails with the identical message -- and the fix is that a suite pass now waits on the gate instead of asking again in a loop, which is the serialisation a mutex is for. The scan is what keeps the product at zero wait, where ninety-nine peers queueing to redo one pass is the thundering herd the design removes. One more test changed shape without moving the count: ReVerificationIndexTests.EveryRowIsEitherManualOrNamesSomethingThatExists now reads around a previously "..." clause the way HazardIndexTests already did, with both gates asking one definition of the clause; it was watched red against the two index rows that had been left quoting dead test names without backticks to work around the asymmetry. The +11 is one path function, and it is a net of 21 arrivals against 14 departures: SessionDirectoryGuardTests is deleted and CanonicalPathTests stands where it stood, asserting a canonical FORM where the old file asserted a refusal. Every alias this machine can build - a \\?\ prefix, a substed drive letter, a junction, a directory symlink, an 8.3 short name - is now resolved into the spelling the filesystem itself uses, and the invariant test is strictly stronger for it: EverySpellingOfOneDirectoryIsAdmittedUnderTheOneIdentity requires each spelling to be admitted and to hash to one identity, where the arm it replaced could be satisfied by a refusal. Six of the new cases are behaviours that were open defects, each watched red first: a listing pointed at a junction over the tree its sessions are in answered "No BrowserAI sessions under '...'" with no error at all; browserai_destroy on a UNC path took 21 seconds to answer that is not a session, because the boundary refusals ran at init and resume only; a subst onto a mapped drive was refused with a spelling that earns the network refusal on the next turn; a trailing dot, a trailing space and a bare NUL were accepted and silently rewritten by Windows; an index entry pointing at a non-canonical spelling was followed and not swept; and a stray-sweep title on a letter substed onto a mapped drive passed the guard that exists to keep the sweep off a redirector. One is a mechanism, not a behaviour: HouseRuleTests.ThePrefixIsDerivedInOnePlaceAndTheRestOfTheTreeAsksForIt reads src/ as text and fails a second derivation of the subtree prefix, which is what closes W8 for good, not for now - watched red against the two real offenders in SessionManager. And one measurement inverted an ordering the whole type was built around: GetDriveTypeW through a substed letter resolves the substitution and blocks, 21,035 ms against a dead share, then answers DRIVE_NO_ROOT_DIR - so VolumeIdentity asks QueryDosDeviceW first now, and only asks GetDriveTypeW about a letter that is not a substitution. What moved for every session on this machine is the spelling and not the identity: the answer and the record carry the drive letter upper-case, because that is what Windows reports, and nothing hashed changed. The +9 is the verdicts file, and what it replaced was one C# constant. Which tools BrowserAI forwards is now tool-verdicts.json -- one row per tool, allow / deny / answer, tracked at the repository root, copied into the payload it describes and read at startup -- and a name with no row is refused at the door. Seven of the nine are ToolVerdictTests: the file against the golden tools-list.json snapshot in both directions, each direction planted red by hand before the check went in and then held by a doctored-file control that asserts the text of the disagreement; the seven authored rows against SessionToolSurface.Names, both ways; every deny carrying the reason a caller reads and an ISO date; judgedAgainst against the committed payload lock; fifteen malformed shapes and a missing file, each refused with the file named, against the undoctored file as the positive control; and the payload copy compared byte for byte with the tracked one. The other two drive the door and the advertised list through rig copies of the file -- a denial the product does not ship, so the shipped deny set stays at exactly one and the four documented surface counts do not move -- and they were watched red against the hardcoded predicate before it was deleted. One test was inverted and not added, and it is the one whose own comment said it existed to prove a removal: AToolThisBuildHasNeverHeardOfIsForwardedRatherThanRefused is now AToolThisBuildHasNeverJudgedIsRefusedRatherThanForwarded. What went on 2026-08-18 was a (tool, mode) permission matrix and every word of why it went still holds; a verdict is a different question -- whether a name this build has never been told about is worth starting a browser for, given that upstream creates the browser context before it looks a tool name up. browser_annotate's withhold dissolved into its own deny row: the refusal a caller reads is byte-identical, because the four measured facts that were a doc comment beside a C# constant are now that row's why, and the error catalogue went from 25 rows to 26 as AnnotationIsNotInTheSurface split into ToolIsDenied(tool, why) and ToolHasNoVerdict() -- two rows because a denial has no fix and a gap is answered by tools/list. SessionToolPolicy.cs is deleted and eight test files read the shipped file instead. And one flake was found by the full run, not by review, and it was not this batch's: four arms in SessionLogTests read a forwarded call's row the instant the round trip returned, and BrowserProxy settles that row in a finally that runs after the answer goes out -- so one read in-flight where it expected failed. They now wait for the row to settle, bounded by the suite's own hang detector, not by any number a test invented. The product ordering is deliberately unchanged: the finally is what covers the ways out the child never hears about, and a neighbouring arm asserts that the in-flight window is genuinely visible to a second reader while a call is outstanding -- so what was wrong was a test asserting a promptness the product never offered. The +14 is a corpse read as a live client, and a release body nobody could reproduce -- 2026-09-15. Change by change, each named with what it was watched red against: ProcessLivenessTests.AWatchIsRefusedWhenThePidOpensAndItsProcessHasAlreadyExited, red in 254 ms at "Expected to be null but found BrowserAI.Interop.ClientLivenessWatcher" against a watcher that read OpenProcess succeeding as there is somebody there -- a launcher that has exited goes on answering while anything holds a handle to it, and the console host does; InstallerHandoffTests.ThePublishedBinaryTreatsALauncherThatExitedButStillOpensAsNobodyToServe, red against the published pre-fix binary at 'received "Watching the MCP client"', and .ARunWithNobodyToServeStartsNothingAndCreatesNothingButItsLog red the same way -- the arm that had failed once in four full runs that morning and cost the whole of TestDefaults.ProcessHang when it did, now failing every time in under 400 ms, because OrphanedConsoleStart produces the openable corpse by construction instead of leaving it to the machine; ChangelogTests.AVersionSectionBecomesHeadlinesWithTheDetailFolded and .ABodyThatDoesNotFitFallsBackToHeadlinesAndSaysSo, each watched red against its own mutation of the new generator -- the fold removed, and the size guard removed, which read "is FOLDED: 291 characters against a limit of 400"; .TheReleaseNotesAnchorIsTheOneTheLinkCheckerComputes, three arguments, red on the one_off heading alone when the PowerShell slug was made to strip underscores, which is the mistake a hand-written slug rule actually makes; .AnEntryNotInTheHeadlineShapeRefusesTheBody and .AVersionWithNoSectionRefusesRatherThanWritingAnEmptyBody, both refusals, each with the accepted fixture beside it as the control; .EveryEntryOpensWithOnePaletteIconAndABoldOneSentenceHeadline, .TheLegendAtTheTopListsExactlyTheApprovedPalette (renamed 2026-09-17 to .TheLegendAtTheTopIsATableListingExactlyTheApprovedPalette when the legend became a table), .EverySectionsGroupsAreTheKeepAChangelogSetInItsFixedOrder and .TheNewestReleasedSectionOpensWithAPreamble, whose subject is the changelog's own shape and whose controls are synthetic because the tree can only ever be in the passing state; and ReleaseScriptTests.TheReleaseBodyIsGeneratedFromTheSectionRatherThanCutFromIt, red at "Expected to be greater than -1" before the wiring went in. DocumentationLinkTests lost no coverage when Slug moved into MarkdownAnchor: the same method, called from two places now, because a link in a published release is the one link here nobody can re-check afterwards.
The +4 is the icon batch, 2026-09-16, and each was watched red against the
thing it names. HouseRuleTests.NoTextFileInTheTreeCarriesAControlByte, red
naming three files by line -- HAZARDS.md:145, BrowserIdleTimerTests.cs:994
and SessionToolTests.cs:95 -- each a \b that something expanded into a literal
backspace before the file was written, and one of them had turned
BrowserIdleTimerTests.TheShippedClockIsTheRealOneAndNothingInTheProductReplacesIt
into a scan that could not match anything: it asked for a backspace before
Clock, so the arm asserting that no shipped file assigns the clock seam was
passing over a result set nothing could enter. That arm now carries a positive
control asserted before the emptiness is, and it was watched red at "Expected to
be true but found False" with the defect put back.
DocumentationLinkTests.EveryAssetReferenceInTheProseResolvesToTheFileItNames,
red at "README.md:4: 'assets/icon-127.png' names a file that is not there" --
the first image this project has published, and the exclusion that would have
hidden a broken link to it had already gone quiet, because
TheAssetExclusionHidesNothing never walked assets\; that arm now asserts the
property the exclusion needs and was watched red against a stray asset planted
outside the directory.
ReleaseScriptTests.TheShippedIconIsTheOneTheMaintainerChose, red at "the
directory declares 3 entries and this icon must carry 4 entries: 16, 32, 48,
256" against the real file with its count doctored -- its controls are doctored
files, not the icon it replaced, because candidate 1 was packed by the
same script and has the identical directory shape, and a check that cannot fail
against the file it replaced is not evidence.
ChangelogTests.ABodyIsGeneratedOnlyFromTheChangelogTheTagCarries, red at
"Expected to be 1 but found 0" with the generator's refusals taken out, over a
real repository built in scratch; the same plant took
AVersionSectionBecomesHeadlinesEachLinkedToItsOwnLineRange and
ABodyThatDoesNotFitFallsBackToHeadlinesAndSaysSo red with it. And one arm
caught the batch's own tooling: the control-byte scan named two backspaces that
a documentation edit made in this very batch had written into RELEASING.md and
TESTING.md, in the same shape it exists for.
The count did not move on 2026-09-16 when the scratch directory was retired,
and one arm changed behaviour anyway, so it is recorded here and not left
out for want of a number.
DocumentationLinkTests.EveryAssetReferenceInTheProseResolvesToTheFileItNames
resolves a reference written in a quoted record -- anything under
docs/ledger/, which is a verbatim snapshot of a file written at the repository
root and sealed against editing -- from the root as well as from beside the file.
It was watched red twice: first by the tree, at "docs\ledger...:2576:
'assets/icon-128.png' names a file that is not there", which is the front
page's own icon quoted inside a ledger; then by planting the new predicate
false and watching the arm's own control fail at "Expected to be true but
found False". The fallback is one named directory and not a skip, and the arm
asserts in both directions that a reference resolving from neither place is
still an offender.
The +17 is one review batch, 2026-09-16, and every arm below was watched red
against the defect it names. ReleaseScriptTests.TheSuitesInstallerIsPackedUnderATestIdIntoADirectoryOfItsOwn,
red at "build/New-Release.ps1 no longer assigns $packTitle as a single-quoted
literal" against two packs that shared one Start Menu .lnk;
RegistrationTests.NeitherAnInstallNorAnUninstallTouchesAnEntryThisInstallDidNotWrite,
red at "Expected to be equal to Refused but received Registered" against an
install hook that overwrote another BrowserAI's registration -- and the same
change turned six existing arms red, which is how it was established that they
had been asking the machine's own ~/.claude.json a question;
ConfigurationAppTests.NothingAClickDoesCanThrowOutOfTheDialogsCallback, red at
"InvalidOperationException: the link threw" escaping a reverse P/Invoke, where
an escaped exception is a FailFast;
.AFolderThatCouldNotBeTurnedIntoAPathIsNotACancel, red at "Expected to be
equal to Failed but received Cancelled";
HouseRuleTests.EveryFolderPickerIsOwnedByTheDialogThatOpenedIt, red naming
Program.cs:186 and its literal 0 owner -- and red a second time, correctly,
the moment the fixed call wrapped across lines and the first version of the
scanner read an empty argument; .NoUpdateCallIsMadeWithAnUnboundedToken, red
naming all three CancellationToken.None sites against a check whose only bound
was Velopack's thirty-minute HttpClient default;
ReleaseScriptTests.AReleaseCutOverLocalPreReleasePacksIsRefusedAndNamesWhatToClear,
red against the script calling a stale local feed a rollback;
AppendOnlyRecordTests.ADateSetAtTheCutIsReportedAsAHeadingRatherThanAsARewrite,
red at REWRITTEN ... revert it for a release date the checklist requires
changing; ChangelogTests.AnEntryAboveTheFirstGroupAndAParagraphInsideOneAreBothRefused,
red at PowerShell's "The property 'Entries' cannot be found on this object";
InstallerHandoffTests.TheInstallersVariablesAreClearedAfterVelopackReadsThemAndBeforeAnythingStarts,
red against a clear that ran before the call whose behaviour it decides; and
ConfigurationAppTests.TheDialogsIconIsAskedForAtTheDialogsDpi, red against a
32-pixel icon stretched under Per-Monitor-V2. Four arms are new assertions
about behaviour that was already correct and unread, and they carry controls,
not a red: the three BackgroundWork arms, and
TaskDialogLayoutTests.TheAppsEmbeddedManifestDeclaresCommonControlsLongPathsAndPerMonitorV2,
whose control is the server's own manifest.
.ThePublishedConfigurationAppRunsInASingleThreadedApartment is a
measurement -- [STAThread] under NativeAOT had been assumed since the
configuration app was written, and it is STA.
LoadIconWithScaleSize, which comctl32 exports by ordinal only, so the
published app threw EntryPointNotFoundException out of Show() -- outside the
callback boundary added in the same batch -- and exited 0xC0000409 without ever
drawing a window. RealInstallerTests.TheInstalledMainExecutableOpensOneDialogAndNoConsoleWindow
polled ten minutes for a dialog that was never going to appear.
The +21 is the two-binary split, 2026-09-15, and each is named with what it
was watched red against. RegistrationTests gains seven: three over the
composed sibling -- the server is registered, not the app that composed it
(red as "expected ...\current\BrowserAI.Server.exe, received ...\current\BrowserAI.exe,
which differs at index 120"), an install with no server beside the app is
refused by name, and a file at the server's name that is not a console binary is
refused (both red as "expected to be false but found True"), each over an
install layout constructed out of PE headers the test writes; three over the
update hook's repair -- ours-and-stale re-pointed, ours-and-valid untouched,
foreign reported and never touched (red as "expected AlreadyRegistered, received
Refused" once the foreign arm existed); and one over the portable project-scope
command. TaskDialogLayoutTests gains four, holding the hand-written
TASKDIALOGCONFIG against Microsoft's own metadata -- 160 bytes, 22 offsets, with
a naturally packed copy at 184 as the positive control -- and reading the PE
subsystem out of both executables. ConfigurationAppTests gains six, which
assert every sentence, link and button of the window without opening one; two of
them found something as they were written, and both are recorded in the log
and not quietly fixed. RealInstallerTests gains two: the installed main
executable opens exactly one visible top-level window of class #32770, owns no
console window and exits 0 on WM_CLOSE, and the packed release names the app as
its mainExe and carries both binaries. ReleaseScriptTests and
BuildConfigurationTests gain one each -- both publishes behind their own ILC
gate, and a configuration app that must declare the version 6 common controls,
the one entry whose absence has no compile-time signal at all.
Corrected 2026-08-17 (previously "Design phase. Nothing is built."). That sentence outlived the design phase by a full build and was the first thing a reader of the public repository met. It is recorded and not quietly replaced because the failure is worth keeping: a status line is written once, at the moment it is true, and nothing anywhere goes red when it stops being true.
What is still owed is in TODO.md; what is still undecided is under Still open.
BrowserAI is source-available under a bespoke variant of the Functional Source License 1.1 (MIT Future License), modified so the Change Date is the fifth anniversary of each release, not the canonical second. On that date the release additionally becomes available under the MIT License. In spirit: read it, run it, modify it, deploy it inside your organisation -- but do not ship a commercial product or service that competes with it, for five years, after which it becomes MIT.
This is not the canonical FSL and must not be referred to by, or distributed under, the SPDX identifier FSL-1.1-MIT. Where an SPDX expression is required, use LicenseRef-BrowserAI-FSL-1.1-MIT-5yr. The authoritative terms are in LICENSE and prevail over this summary.
Copyright 2026 Jori Huisman.
Source files carry the two-line SPDX header -- SPDX-FileCopyrightText plus SPDX-License-Identifier, always in the LicenseRef-BrowserAI-FSL-1.1-MIT-5yr form, as AGENTS.md requires and as every article under kb/, AGENTS.md and UPSTREAM-REVIEW.md already do.
The licence does not demand it -- LICENSE is the notice and shipping it satisfies the Redistribution clause -- so this is a house rule, kept for a different reason: a file that names its own licence cannot be copied out of the repository and quietly become unlicensed, which is exactly what happened to the launcher this project replaces, thirteen times.
Formats with no comment syntax carry it as data where they can (upstream-review.json has a _license key) and not at all where they cannot. Vendored third-party files keep their upstream headers instead, which Apache-2.0 §4 requires.
The license above covers BrowserAI's own code and its documentation. It does not cover the bundled payload, which keeps its own terms. Shipping that payload creates obligations that attach at first installer handoff, independent of BrowserAI's own license. Verified 2026-08-14 against the versions pinned in the runtime, re-read 2026-09-17 @ @playwright/mcp 0.0.81 · Node v24.21.0 and the browser revisions the second table's rows state, and corrected here 2026-09-18; what is actually present in each shipped tree is recorded in kb: payload licensing:
Two columns of this table used to be one, and merging them was the error. What BrowserAI redistributes is the installer payload, and only that creates obligations for us. What the user's machine downloads on first run comes from Playwright's CDN, direct to that machine, and carries no redistribution duty for us at all -- we ship no copy of it, so there is nothing to accompany with a licence. That is not a side benefit of first-run provisioning; it is the reason for it.
What we redistribute -- obligations are ours:
| Component | Terms | Obligation on redistribution |
|---|---|---|
@playwright/mcp |
Apache-2.0 | Keeping the vendored node_modules tree intact ships the package's LICENSE and satisfies §4. It carries no NOTICE of its own, which is true of this package and was never true of the other two. Scope forbids modification, so §4(b) is clean by construction. |
playwright-core, playwright |
Apache-2.0 | The same vendored tree and the same §4 -- but §4(d) does bite: each of the two ships a NOTICE of its own, a ThirdPartyNotices.txt and three per-bundle *.LICENSE sidecars, and shipping the tree intact is what propagates them. playwright is @playwright/mcp's other exact dependency and has been in the payload since the first build of one; its LICENSE and NOTICE are byte for byte playwright-core's, so what was ever missing here was a name and never a licence. Corrected 2026-09-18 @ @playwright/mcp 0.0.81 (previously "@playwright/mcp, playwright-core 0.0.79"), and the §4(d) sentence with it (previously "Upstream publishes no NOTICE file, so §4(d) has nothing to propagate") -- measured 2026-09-17 against a real publish: 254 bytes each, byte-identical to one another. The resolved versions are deliberately not repeated here. THIRD-PARTY-NOTICES.txt states them and ThirdPartyNoticeTests reads them back out of build/payload/package-lock.json, which is the difference between a number that goes red when it goes stale and the one this cell carried for a month. |
ModelContextProtocol, ModelContextProtocol.Core 2.2.0 |
Apache-2.0 | §4(a): a copy of the licence must reach every recipient. Compiled into BrowserAI.exe, so nothing carries it unless we do -- it ships in THIRD-PARTY-NOTICES.txt. Upstream's own LICENSE grants three licences, not one: Apache-2.0, MIT for contributions whose authors have not consented to relicensing, and CC-BY-4.0 for documentation. It is reproduced whole for that reason, and because upstream's copy ends at END OF TERMS AND CONDITIONS and omits the appendix its own §4 refers to, which is upstream's file as published and is not completed here. Vendored fixture files keep their upstream headers. |
Microsoft.Extensions.* -- 17 assemblies |
MIT | Notice, same as Velopack's and for the same reason. Two referenced directly (Logging, Logging.Console), the rest transitive; the list is derived from src/BrowserAI/packages.lock.json by ThirdPartyNoticeTests and not typed, so a package entering the closure on a later bump is a red build. Two copyright lines, because sixteen come from dotnet/dotnet and Microsoft.Extensions.AI.Abstractions from dotnet/extensions. |
| Velopack 1.2.161 | MIT | Notice. Corrected 2026-10-12 (previously "Velopack 1.2.0"): 1.2.161 is what src/BrowserAI/packages.lock.json resolves and what THIRD-PARTY-NOTICES.txt names. |
| RegisterAI | LicenseRef-RegisterAI-FSL-1.1-MIT-5yr, the same author's |
Its licence is compiled into payload\registerai\RegisterAI.exe and printed by RegisterAI.exe license, and THIRD-PARTY-NOTICES.txt names it. Added 2026-10-03, when registration started running through it. |
| Node.js v24 | MIT, plus aggregate terms for OpenSSL, ICU, V8, zlib, c-ares | Ship Node's full LICENSE. "A single node.exe, nothing else" drops it. Not optional. |
What the user's machine downloads -- no obligation on us:
| Component | Terms | Position |
|---|---|---|
full chromium 1247 (155.0.8059.12) |
Google Chrome for Testing -- Google-branded, no OSS license file anywhere in the tree | Provisioned on first use, fetched by playwright-core from Playwright's CDN into %LocalAppData%\BrowserAI\browsers\chromium-<revision>\. Corrected 2026-10-03 (previously "full chromium 1246 (154.0.8037.0)"): the first roll since 1244 to move browserVersion, so a new archive and a new tree, and both were re-read that day and not carried forward. The only licence-adjacent file among its 307 is still ABOUT, 257 bytes and byte-identical to 1246's, chrome.exe still reports CompanyName "Google LLC", and the one file the tree lost is dxil.dll. chrome.exe reports CompanyName "Google LLC" and "Copyright 2026 Google LLC. All rights reserved."; its ABOUT points at Google's Chrome Terms of Service, and the only on-point public statement is a Google representative naming which terms apply: Mathias Bynens, a GoogleChromeLabs member, in chrome-for-testing issue #21 on 2023-06-16 -- "Chrome for Testing is a flavor of Google Chrome, so https://www.google.com/chrome/terms/ applies" -- and, in the same thread, that the repository's Apache-2.0 file "applies to the code in this repository" and not to the binary. Cited 2026-09-23. Corrected 2026-09-23 (previously "a Google engineer, 2023 -- reads those terms as forbidding redistribution"): Google stated the pointer and nothing further; the "so unfortunately no redistribution is possible" sentence belongs to the person who asked, and Google did not answer it. Those terms defer to Google's Terms of Service, whose Software in Google services section reads "You may not copy, modify, distribute, sell, or lease any part of our services or software" -- and reading that against this product is a legal question nobody here may answer, which is why we do not redistribute it and provisioning stays a first-run download. We do not redistribute it, which is precisely why the provisioning decision was taken; the row that used to read "Unresolved" described a blocker that decision already closed. What remains open is only the bundled-build fallback, and it stays closed until someone gets a different answer from Google. Corrected 2026-09-21 (previously "full chromium 1245"), and corrected 2026-09-18 before that (previously "full chromium 1237") -- 1237 is what the payload resolved on 2026-08-14 and the revision has rolled three times since, always at the same browserVersion 154.0.8037.0, so the archive behind every one of them is the same archive. The revision moves with the payload and this cell moves with it: ThirdPartyNoticeTests reads it back out of upstream-snapshots/browsers.json, which the build regenerates from the resolved payload, so a roll that leaves this number behind is a red build, not a cell nobody re-read. The reading itself was re-taken at 1245 on 2026-09-17 and not carried forward: the only licence-adjacent file among the tree's 308 is ABOUT, 257 bytes. |
firefox 1553 (156.0) |
MPL-2.0 headline, with Apache and BSD terms besides | Provisioned on first use exactly as Chromium is, from the same CDN into %LocalAppData%\BrowserAI\browsers\firefox-<revision>\. Corrected 2026-10-03 (previously "firefox 1549 (156.0)"): a rebuild of the same 156.0, re-read that day and not carried forward -- still 63 files with no standalone licence file, and both license.html files below are byte-identical to 1549's. There is no standalone licence file in the tree at all -- 63 files, enumerated and not searched (corrected 2026-09-21, previously "61 files"). The terms travel inside the archive: chrome/toolkit/content/global/license.html at 320,546 bytes within firefox\omni.ja, and chrome/browser/content/browser/license.html at 320,988 bytes within firefox\browser\omni.ja, which is what about:license renders. A bundled build would have to extract and carry them, which is harder than accompanying a file. Added 2026-09-18, measured the day before (previously "firefox 1548 (155.0)") (kb): Firefox has been a provisioned family since 2026-08-19 and this table did not list it -- the same omission THIRD-PARTY-NOTICES.txt carried until the day before, one document across. ✅ RE-MEASURED 2026-09-22 AT 1549 / 156.0, AND THE SIZES MOVED -- corrected 2026-09-22 (previously "320,171 bytes ... 320,613 bytes", and before that the warning that the revision had moved and the reading had not: "the byte counts above are the previous reading and are owed, marked [STALE] on row 26"). +375 B on each, and the 375 bytes are three additions to existing licences' file lists: gfx/graphite2 under LGPL-2.1, third_party/dav1d under BSD 2-Clause, and Microsoft.WindowsAppRuntime.dll with Microsoft.WindowsAppRuntime.Insights.Resource.dll under MIT -- the last two being exactly the two new files that took this tree from 61 to 63. No licence was added, removed or changed and the MPL-2.0 headline is unmoved. This is the case the [STALE] marker was put there for: a new browserVersion is where this file can change, it did, and nothing was carried forward as though it had not (kb). |
chromium-headless-shell 1237 |
LICENSE.headless_shell and the credits file would have to accompany a bundled build; neither is our problem today.BrowserProvisioner passes --no-shell to upstream's own install-browser and has since a45749b on 2026-08-16, two days after this row was written; the browsers root held no chromium_headless_shell-* directory at any revision when it was read on 2026-09-17. Struck and not deleted, and its revision left at 1237 and not rolled: 1237 is what the tree it describes was pinned at, the bundled-build total still counts its 268.49 MB, and a number about a tree nobody has must not be updated as though somebody did. |
|
ffmpeg 1011 |
LGPL-2.1 | Arrives beside either browser from the CDN -- both families fetch it, into the same root. COPYING.LGPLv2.1 ships in that directory as Playwright lays it down, 26,526 bytes of a 4-file tree. Spawned as an unmodified separate executable by playwright-core, so §6's relink requirement does not bite and it never reaches BrowserAI's own code. A bundled build would owe version identification and an offer of corresponding source. Corrected 2026-09-18 (previously "Arrives beside Chromium from the CDN"), verified 2026-09-17 unmoved at revision 1011 -- it read as Chromium's because Chromium was the only family when it was written. |
winldd 1007 |
no license file shipped | Same: downloaded by either family, never redistributed, and the tree is small enough to state exhaustively and not search -- 3 files, DEPENDENCIES_VALIDATED, INSTALLATION_COMPLETE and PrintDeps.exe, verified 2026-09-17 unmoved at revision 1007. A bundled build would have to source a licence from microsoft/playwright first -- which is a reason not to bundle, not an outstanding task. |
Under a bundled build the four live rows above move into the first table and their obligations become live; the struck one does not, because there is nothing anybody has to bundle. Nothing in the second table blocks a release today. Corrected 2026-09-18 (previously "all four rows above") -- the table is five rows now, one of them struck and one of them new.
Playwright is a trademark of Microsoft Corporation. Chrome and Chromium are trademarks of Google LLC. BrowserAI is not affiliated with, endorsed by, or sponsored by either. Apache-2.0 §6 grants no trademark rights, and the inherited browser_* tool names surface upstream branding directly in BrowserAI's own API -- ship a short disclaimer in the installed artifact.
✅ It ships, since 2026-08-16, in THIRD-PARTY-NOTICES.txt beside the binary, together with Velopack's MIT licence -- two of the obligations with no upstream file of their own, and the two that the first run of the release checklist found absent from an otherwise releasable package. ThirdPartyNoticeTests asserts them against the repository file, the publish output and the packed .nupkg's entry list, so an obligation added to the table above is a red build, not a discovery at the next release.
✅ Both tables above are held to those same sources from 2026-09-18, which is what the two corrections dated that day are: ThirdPartyNoticeTests.TheReadmeTablesNameEveryPackageThatShipsAndEveryFamilyThatIsProvisioned reads build/payload/package-lock.json, ProvisionedBrowsers.Families and the committed upstream-snapshots/browsers.json -- so a package that ships and is named nowhere here, a provisioned family with no row of its own, and a browser revision a roll left behind are each a red build, not something a reader has to notice. What no test here reads is the prose inside a cell. Whether the terms a row names are the terms actually in that tree is a measurement, it is dated in the row that states it, and re-taking it is row 26 of the re-verification index. A previously "..." span is cut out before the revisions are read, because a correction stamp is a record of what a cell used to say and holding it to today's manifest would demand the record be rewritten at every roll.
Corrected 2026-08-16 at the plan's final audit: four such obligations, not two (previously "the two obligations with no upstream file of their own" and "asserts all four"). The MCP SDK and the Microsoft.Extensions.* family are compiled into BrowserAI.exe on exactly Velopack's terms, and a NuGet package's licence stays in the machine's package cache -- it is never copied to a publish output, so linked in and its notice ships are independent facts and the second was false for both. Apache-2.0 §4(a) is the stricter of the two clauses, not the looser, and this product is publicly distributed. The Microsoft.Extensions.* list is derived from src/BrowserAI/packages.lock.json and not typed, so a package entering the closure on a later bump is red here, not a licence nobody noticed had arrived.