Skip to content

feat(computer): browse watched sites in a window of the user's everyday Chrome - #44

Merged
jeffrey-bin merged 31 commits into
mainfrom
feat/humanlike-input-fidelity
Nov 12, 2026
Merged

jeffrey-bin merged 31 commits into
mainfrom
feat/humanlike-input-fidelity

Conversation

@jeffrey-bin

@jeffrey-bin jeffrey-bin commented Nov 11, 2026 •

Copy link
Copy Markdown

Summary

Sites that watch for automated browsing are now browsed in a window of the Computer's own in the user's everyday Chrome, instead of a separate Chrome profile. That window carries the user's sign-ins, history and device trust. Input stays OS-level through Cua, with no CDP.

  • launch_app with agent_browser opens one site per call in a new Chrome window, through Chrome's command line, in the profile Chrome last used. The Computer's window is the new one-tab window that shows the site just opened, or the only new window when the home page moves to another site. Two new windows of the site are refused with COMPUTER_WINDOW_AMBIGUOUS.
  • Tabs and the address bar are found by role and place in the window, not by names, so a Chrome in any language works.
  • A covered window keeps rendering and reading only when Chrome runs with --disable-backgrounding-occluded-windows.
    • A Chrome that is not running starts with this flag, as its own instance (open -n), after its window list stops changing.
    • A Chrome that runs without it returns COMPUTER_BROWSER_RESTART_REQUIRED. The Daemon never quits the user's Chrome: the Agent asks the user to quit it, and the next launch_app starts it with the flag and --restore-last-session. The question is recorded in computer-tools/everyday-chrome-quit, so a Daemon restart keeps it.
    • Chrome is found by its executable wherever it is installed.
  • The user's Chrome takes no keys from the Computer (COMPUTER_KEYS_REFUSED) in any of its windows: tool calls, run_actions steps and scripts.
    • In the background, a shortcut runs as a menu command in the Chrome window that has the keyboard.
    • Cua types text as keys that all read as the A key.
    • A key named by its element_token goes to the app the token was read from, and a key that names no app is refused while that Chrome runs.
    • Not even Return bound to a search box goes: Cua refuses a background key while Chrome has another window, which the user's Chrome always has. A script does not start an app.
    • The Agent fills fields with set_value, presses the page's buttons, and goes back through the page's links.
  • The Computer's windows in the user's browsers are kept per browser, so a Safari window does not replace the Chrome one.
  • The retired separate profile, computer-tools/agent-chrome, is removed in the background when the Daemon starts, after its Chrome is quit.
  • The Agent's read tokens carry over the Computer's own reads (the tab check before an input). A read no longer fences off the reads before it, so a token from the Agent's latest read is not staled by the check.
  • A script's getState takes no screenshot unless it passes include_screenshot: true. A script reads text, and Cua's default screenshot cost seconds on a busy page: script reads on Taobao went from 9.9 s to 1.1 s on average.
  • A script's reads after its last input carry its tokens into the next script. The guard returns those reads beside the script's value, and the Daemon records their rows and puts the value back, so the tab check between scripts no longer stales a token the script returned.
  • An input in the Computer's own browser now says what it changed, from the tab read the Daemon already makes after it: a tab opened or closed, or a new front tab title. A new tab's note names its Close button and the read that reaches the tab strip past the page. A click on an element whose row lists no press notes that the press reaches it anyway and that a repeat by x,y waits for a read showing no effect, because Cua's "may have been a no-op" warning sent Agents to x,y clicks that missed. A name of only white space is refused rather than matching an element with no value. A role and name target matches a label with a no-break space. Over four Taobao runs, failed calls went from 4 to 0 and tab closes from 3–4 calls to 1.
  • The Daemon reads the browser's tab strip only as far as it needs. Before an input it compares the window title from list_windows with the front tab's title, and reads the strip only when they differ; after an input it reads only to the strip's depth, unless a script returned reads, and not at all after a script with no input. A bounded read that shows fewer tabs is read again in full, and launch_app takes the front title from list_windows instead of a second whole read. A click on Taobao went from about 3.4 s to 1.3 s, and the fastest Taobao run from 369 s to 308 s.
  • recognize_text is removed. Agents called it twice in about a thousand Computer tool calls, both after a screenshot that already showed the text. Its paged read sends keys, which the user's Chrome refuses.
  • humanlike-browse 1.0.8 carries the new route, the question to quit Chrome, how to work without keys, when a screenshot is worth its cost, and what to do after COMPUTER_TAB_CHANGED.
  • A change of tabs between the task's inputs refuses inputs to that window until the Agent reads it with get_window_state, instead of ending the task: a tab the task's own input opened late looks the same as one someone else opened. Each window opened with launch_app keeps its own tab count.
  • The browser tools' site note now says to use the computer tools on these sites, never the browser tools.

Why

Measured on a local event-logging page (Cua 0.35.4):

Input What the page sees
Background click (AX press; background pixel clicks become AX too) No pointer movement, pressure 0, click buttons 1, integer coordinates, screenX that does not match the window
Foreground real pointer with a path Realistic: pointer path, pressure 0.5, click buttons 0, fractional coordinates
Mouse events posted to a background Chrome window (CGEventPostToPid) Nothing; Chrome drops them
Covered everyday Chrome window, without the flag visibilityState hidden, rendering stops, and the AX tree misses new content
Background cmd+r with window_id Ran as the menu command in Chrome's key window, not in the target window
Typed text from type_text, background and foreground Every letter has code KeyA

Benchmark context: browser-use (CDP) in the everyday Chrome got Taobao's "访问异常" page within a few navigations. computer-use in a separate profile was not blocked, but it starts without the user's device trust.

Validation

  • go test ./internal/linger/localtools/ passes on macOS. New tests cover:

    • the quit question, the start with the user's windows, and the new window beside a late restored one;
    • two new windows of the site, refused;
    • keys refused in the Computer's window and the user's, by token window, with no pid, in paged reads and in the script guard;
    • finding Chrome in any folder, and the key rule shared by tool calls.
  • Two review rounds (code-review-expert) found 14 issues and 4 follow-ups; all are fixed in this PR.

  • make check-computer-go and make -C apps/teamhub lint-go-daemon pass. Cross-builds pass for darwin, linux and windows.

  • make check-repository-gate: everything passes except scripts/ci/desktop-release.test.ts, which fails the same way on main.

  • TeamHub go test ./internal/skill/... passes.

  • Live runs below used the earlier restart-by-consent flow; the window, key and pacing behavior they exercised is unchanged.

  • Live runs on a bench environment: a stand-in Chrome on a test profile played the user's Chrome, through a bench-only daemon build.

    • The restart question card appeared. After consent, the test Chrome restarted with the flag and restored its windows.
    • airbnb-ask ran end to end in the Computer's window with no keys, in 843 s with the 35k follow-up and no block.
    • reddit-amazon got a captcha on Reddit's home page in the fresh test profile, which has no history. The Agent reported it and stopped.
    • The first launch failed because the test profile showed Chrome's sign-in screen. This led to the fix that names the last used profile.
  • Live runs in the user's real everyday Chrome, after the restart card was approved: Chrome restarted with the flag and restored the user's windows.

    Case Result
    taobao-detail 367 s, complete: orders, delivery, price and 60 reviews. Not blocked. In the same Chrome, browser-use (CDP) got Taobao's "访问异常" page after 144 s.
    reddit-amazon 398 s. Reddit was read in full. An Amazon product page then showed an "I am human" checkbox; the Agent reported it and stopped with partial data.
    airbnb-ask 764 s, including the wait for the restart card. Complete, not blocked.

This PR adds no network round trip or file walk on the user message → provider_ready path.

🤖 Generated with Claude Code

@jeffrey-bin
jeffrey-bin marked this pull request as ready for review November 12, 2026 01:10

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes (2 major, 3 minor, 1 nit)

Scope: base main, merge base 3d9c303; 2 commits, 11 files reviewed at 1f1f029. Helper skills loaded: coderbook; ponytail-review and karpathy-guidelines were applied from their checklists. Ran in a separate session.

Findings

Major

1. Restarting the user's everyday Chrome is gated only by a model-supplied boolean — apps/computer/local-daemon/internal/linger/localtools/agent_browser.go:372
launchEverydayChrome SIGTERMs the user's Chrome whenever restart_browser is true. Nothing on the Daemon side checks that a COMPUTER_BROWSER_RESTART_REQUIRED was returned to this operation, or that the user answered a card. The tool schema in cua.go advertises the flag to every caller. If the model passes restart_browser: true on its first launch_app call, which models do with optional flags, the user's main browser quits with no question asked. Text typed into pages and not yet sent is lost, and so is any state that --restore-last-session does not bring back. The PR says Chrome "restarts only after the user agreed", but that is only a prompt instruction. This is the most destructive action in the change, so consent should be deterministic: record the user's answer on the Daemon/TeamHub side, for example through the existing permission-request card flow (internal/permission/shell.go), and accept restart_browser only against that record. At minimum, accept it only after this operation was refused with RESTART_REQUIRED.

2. The "no keys to the user's Chrome" rule holds only for the Computer's window, not for the Chrome process — apps/computer/local-daemon/internal/linger/localtools/cua.go:442 (with agent_chrome.go:434)
everyday := paced && c.everydayChrome(...) applies only when isAgentChrome is true. For the shared browser, isAgentChrome returns true only when no window is named or the named window is sharedWindow. A call such as hotkey {pid: <everyday Chrome pid>, window_id: <one of the user's windows>, keys: ["cmd","w"]} is not paced and not refused. A run_script whose args name a user window gets noKeys = false in the same way. The PR's own measurements show that a background shortcut runs as a menu command in the Chrome window that has the keyboard, so this is exactly the "a key reaches the window the user is typing in" case the change sets out to prevent. The PR body claims "The user's Chrome takes no keys from the Computer (COMPUTER_KEYS_REFUSED), in tool calls and in scripts", and the diff does not back that claim. Fix: key the refusal on the target pid being the user's everyday Chrome (sharedBrowserPID with the Chrome bundle), whatever the window_id, and add a test with a user window id.

Minor

3. Two copies of the key rule have already drifted — agent_browser.go:500 vs cua_script.go:84
The Go guard allows press_key with modifiers: [], with a non-array modifiers (.([]any) fails, so its length reads as 0), and with any key string. The script guard refuses any modifiers field and a pressKey key that contains +. One rule, two implementations, different answers. Align them on the stricter form (refuse when modifiers is present at all) and cover both in the existing tests.

4. The Computer's window is the first window that was not there before, after a fixed 2 s settle — agent_browser.go:412 (settle at :465)
After a restart, Chrome restores windows one after another. A user with many windows can have a restored window appear after everydayChromeSettle. A window the user opens during the wait has the same effect. Either one has tabs, so it is picked as the Computer's window, recorded as sharedBrowserWindow, and the Agent then clicks in the user's own window with their sessions. Matching the new window by the URL that was just opened, through its tab or address bar, removes the race and the sleep.

5. Hard-coded Chrome path — agent_browser.go:313
everydayChromePID matches only /Applications/Google Chrome.app/.... A Chrome installed in ~/Applications (common without admin rights) is never found while it runs. The Daemon then calls open -g -b com.google.Chrome --args --disable-backgrounding-occluded-windows, which a running Chrome ignores, polls for 30 s, and fails with the misleading "the user's Chrome did not start". Resolve the running instance by bundle (as lsappPID does) or by the app path that Launch Services reports.

Nit

6. localtools_test.go:1861: the new test was inserted between the doc comment of TestLocalToolCuaMacAgentChromeOpensAFreshWindowForAnUnreadableOne and that function. The new test now carries the old comment, and the old test has none.

Verified as fine

  • retireAgentChromeProfile deletes computer-tools/agent-chrome once its Chrome no longer runs, which matches the repo's rule to hard-cut retired state on users' machines. ownBrowserPIDs skips the shared Chrome, so the empty Directory is never used.
  • The agentSafari* → sharedBrowser* rename is applied consistently. sharedBrowserRunning checks Chrome via ps and Safari via lsappPID.
  • everydayChromeProfile validates last_used against the existing chromeProfilePattern before passing it on the command line, so Local State cannot inject arguments.
  • The URL still passes homePage/siteOf and the site journal before launchEverydayChrome. Pace and lease handling are unchanged.
  • humanlike-browse 1.0.5 is a new immutable version; 1.0.4 is untouched and catalog.yaml bootstraps 1.0.5.
  • The README and the tool guide were updated with the behavior change.

Process notes

  • The branch is BEHIND main, which requires up-to-date branches, so it needs a rebase.
  • Both commits (ca404b6, 1f1f029) have an author email that is not linked to a GitHub account.

CI

All checks pass, including check (computer-go). That job runs on ubuntu-latest, so the macOS-only TestLocalToolCuaMacEverydayChromeOpensTheComputersWindowAndTakesNoKeys is skipped in CI. Locally on Linux, go test -count=1 -p 4 ./internal/linger/localtools/ -run 'ScriptGuard|Everyday|AgentChrome|Safari' passed. TestScriptGuardRefusesKeysForTheEverydayChrome ran under node and passed; the macOS test skipped here as well.

Automated review by Claude Code


Automated review of 1f1f029 by Claude Code.

jeffrey and others added 5 commits November 12, 2026 10:30
…ay Chrome

The Computer's browser for sites that watch for automated browsing is now
a window of its own in the user's everyday Chrome, with the user's
sign-ins, history and device trust, instead of a separate Chrome profile.

- launch_app with agent_browser opens one site per call in a new window
  through Chrome's command line, so no key reaches the user's windows.
- A covered window keeps working only with
  --disable-backgrounding-occluded-windows. A Chrome running without it
  returns COMPUTER_BROWSER_RESTART_REQUIRED until the Agent passes
  restart_browser after the user agreed; the restart restores the session.
- The user's Chrome takes no keys from the Computer
  (COMPUTER_KEYS_REFUSED): shortcuts run as menu commands in the window
  that has the keyboard, and Cua types text as keys that all read as the
  A key. Only a plain key bound to an element goes. Scripts there are
  guarded the same way.
- The retired separate profile, computer-tools/agent-chrome, is removed
  when the Daemon starts and that Chrome no longer runs.
- humanlike-browse 1.0.5 fills fields with set_value, presses the page's
  buttons and asks before a restart.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…e last used

A user with several Chrome profiles may get a profile picker instead of a
window when Chrome is started without a profile.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…e its keys in every window

The Daemon no longer quits the user's everyday Chrome on a model-supplied
flag: it asks the Agent to have the user quit Chrome, and starts it with the
occlusion flag and the user's windows once it is closed. Keys are refused by
the target pid, so no window of the user's Chrome takes one, in tool calls,
paged reads and scripts. The Go and script key rules now agree, the
Computer's window is the new one-tab window that shows the site just opened,
and Chrome is found wherever it is installed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…ny language and from any script

Tabs and the address bar are found by role and place, not by names that
follow the user's language. Chrome starts as its own instance and the window
list must settle first; the Computer's window is the new one-tab window of
the site, or the only new window, and two of the site are refused. Keys are
placed by the window their token was read from, a key that names no app is
refused while the user's Chrome runs, and every script checks keys against
that Chrome's pid. Shared browser windows are kept per browser, the quit
question survives a Daemon restart, and the old agent Chrome is quit before
its profile is removed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…t by its executable

A script cannot prove which window an element token was read from, so it
sends the user's Chrome no key at all and does not start Chrome. Chrome is
found by the executable column of ps, so an install path with spaces or
dashes still matches. The old agent Chrome is retired in the background, and
a tab's Close button beside it no longer takes a button above its level.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@jeffrey-bin
jeffrey-bin force-pushed the feat/humanlike-input-fidelity branch from 1f1f029 to ac0a4aa Compare November 12, 2026 03:18

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes (1 major, 1 minor)

Scope: follow-up on 1f1f029. Base main, merge-base b4cea4c. The branch was rebased, but git range-diff shows the first two commits unchanged (ca404b6→8dfca31, 1f1f029→9e59cde), so this review covers the three new commits 67f9070, 645c267 and ac0a4aa (git diff 9e59cde HEAD: 12 files). Helper skills loaded: coderbook, ponytail-review, karpathy-guidelines. The review ran in a separate session.

Previous findings

  • Resolved: the restart was keyed on a model-supplied restart_browser. That flag and stopEverydayChrome are deleted. launchEverydayChrome now returns everydayChromeQuit and never quits Chrome, and the user quits it (agent_browser.go:345).
  • Resolved: keys were refused only for the Computer's window. userChromeKeys (agent_browser.go:624, called from cua.go:400) now refuses by the user's Chrome pid, takes the window from the element_token's read, and also covers run_actions steps (they re-enter call) and paged reads.
  • Resolved: the Go and script key rules disagreed. The Go rule is now explicit, and the script refuses every key to that Chrome on purpose, as the comment on guardedScript says. See Major 1 for a gap in the script's method form.
  • Resolved: the window was identified only as "not there before". everydayChromeWindow (agent_browser.go:431) now matches the address bar's site, waits for the window list to settle, and refuses two matching windows with COMPUTER_WINDOW_AMBIGUOUS.
  • Resolved: the Chrome path was hard-coded to /Applications. everydayChromeProcess (agent_browser.go:325) matches the ps comm suffix, and TestEverydayChromeProcessFindsChromeInAnyFolder covers it.
  • Resolved: a new test sat between an old test's comment and its function. The comment is back on TestLocalToolCuaMacAgentChromeOpensAFreshWindowForAnUnreadableOne (localtools_test.go:2040).

Findings

Major

  1. The script guard lets keys through an app object without a numeric pid (apps/computer/local-daemon/internal/linger/localtools/cua_script.go:90).
    • For the cua.call(...) form, keyed refuses a key when pid is missing or not a number. For the method form (app.hotkey, app.pressKey, app.typeText), it refuses only when o.pid === chrome. An app object whose pid is undefined or named differently fails open.
    • The README and the commit message promise that "a script sends that Chrome no key at all" and that "a key that names no app is refused".
    • I checked this with the PR's own guardHarness and chrome = 1. Both const apps = await cua.listApps(); return apps[0].hotkey(["cmd","w"]); and const app = await cua.getApp({name: "Google Chrome"}); return app.hotkey(["cmd","w"]); returned hotkeyed, and the log shows hotkey:cmd+w was sent.
    • The guard cannot see which app such an object belongs to. If the real Cua app object does not expose pid under that name, or the script gets Chrome by name, cmd+w reaches whichever Chrome window has the keyboard. That is exactly what this PR sets out to prevent. Before this PR, the method form was refused without condition.
    • Fix: make the method branch match the call branch: keyMethods[k] === true && (typeof o.pid !== "number" || o.pid === chrome). Add the two cases above to TestScriptGuardRefusesKeysForTheEverydayChrome.

Minor

  1. The humanlike-browse 1.0.5 Skill still points the Agent at a key that scripts refuse (skills/builtin/humanlike-browse/1.0.5/SKILL.md:35).
    • The Skill says to "Put each page visit in one run_script" (line 33). It also says "only Return bound to the search box of the Computer's window goes".
    • The script guard now refuses every key to the user's Chrome, including that Return. MacToolGuide says "never from run_script", but the Skill does not.
    • An Agent following the Skill writes app.pressKey("return", ...) in a script, gets COMPUTER_KEYS_REFUSED, and loses a turn while the user watches.
    • Fix: add "as its own press_key call, never in run_script" to that bullet, or drop the Return exception from the Skill.

Verified as fine

  • run_actions steps go back through c.call, so userChromeKeys covers each key step. Paged recognize_text reads are refused for the user's Chrome.
  • The quit marker computer-tools/everyday-chrome-quit is written with 0o600 under privatefs.EnsureDir and removed once Chrome has started. The README documents it.
  • sharedWindows per bundle replaces the single shared-browser triple. agentChromeRunning iterates in sorted order, and all access goes through m.mu.
  • retireAgentChromeProfile now runs in a goroutine with a bounded context, so it does not delay Daemon start.
  • countTabs no longer depends on localized "Close" labels and skips radio buttons inside AXWebArea. TestCountTabsReadsChromeEdgeAndFirefoxStrips passes.
  • The README section was updated with the new flow, and no Chinese was added to code or docs.

Process notes

  • The commits are authored by jeffrey <jeffrey@simplexai.com>, which is not linked to a GitHub account.
  • The branch is up to date with main (0 commits behind). The merge state is BLOCKED, pending checks and review.

CI

  • Passing: repository-gate, CodeQL, and the installer, packages, teamhub-cli, web and contracts checks.
  • Still pending when I checked: check (desktop), check (computer-go) and check (teamhub).
  • What I ran locally (Linux): go test -count=1 -p 4 ./internal/linger/localtools/ passes. The script-guard, countTabs, everydayChromeProcess and key-rule tests ran and passed. The macOS-only everyday-Chrome tests were skipped on this host, so the window-finding and quit flow were verified only by reading the code.
  • I also ran a throwaway probe test (deleted afterwards) that reproduced Major 1.

Automated review by Claude Code


Automated review of ac0a4aa by Claude Code.

A key sent through an app object whose pid the script guard cannot read
could reach the user's Chrome, so it is refused while that Chrome runs, as
a key that names no app already is. The humanlike-browse Skill says the one
allowed Return goes as its own press_key call, never inside run_script.

Co-Authored-By: Claude Code <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on ac0a4aa. Reviewed the 1 new commit (e8ab40a, 3 files) against ac0a4aa, with the base branch main. Helper skills coderbook, ponytail-review and karpathy-guidelines were not loaded in this follow-up pass, because the delta is a single-line guard change plus one doc line. The review ran in a separate session.

Previous findings

  • Major: script key guard let an app object without a numeric pid through. Resolved. At apps/computer/local-daemon/internal/linger/localtools/cua_script.go:90, the method form of keyed now refuses when typeof o.pid !== "number", the same rule the call form already used. TestScriptGuardRefusesKeysForTheEverydayChrome adds two cases: listApps()[0].hotkey and getApp({name: "Google Chrome"}).hotkey. Both now expect COMPUTER_KEYS_REFUSED. In the harness those App objects have pid === undefined, and the old code returned hotkeyed for them, so these cases fail without the fix.
  • Minor: the Skill said Return may go, but scripts refuse every key to the user's Chrome. Resolved. skills/builtin/humanlike-browse/1.0.5/SKILL.md:35 now says Return goes "as its own press_key call and never inside run_script". This matches the guard's doc comment (cua_script.go:71-76) and everydayChromeKeys.

Findings

None.

Verified as fine

  • o in keyed is always the object wrap was built on, so it is non-null, and o.pid cannot throw. When o is the root cua object, it has no pid, so key methods called directly on it are now refused while the user's Chrome runs. This is the same conservative choice the call form makes for args without a pid. It refuses more than strictly needed and never leaks a key.
  • The guard still lets keys through to an app whose pid is a known number other than Chrome's (pid: 2 cases still return hotkeyed / pressed). Other input to Chrome, such as click, is unaffected.
  • The Skill change touches only the not-yet-released 1.0.5 version, so no immutable version was edited.

Process notes

None. mergeStateStatus is BLOCKED while required checks are still pending.

CI

  • Passed: CodeQL, Analyze (actions), changes, and check for installer, packages, teamhub-cli, web and contracts.
  • Still pending when this review ran: check (desktop), check (computer-go), check (teamhub) and repository-gate.
  • Ran locally: go test -count=1 -p 4 -run TestScriptGuard ./internal/linger/localtools/ passes.

Automated review by Claude Code


Automated review of e8ab40a by Claude Code.

…ow in the user's Chrome

A script key whose arguments name an app or window of their own is refused,
since Cua lets them override the app the call is made on, and no script
opens an app or URL, so the key rule knows every app a script reaches. Every
window the Computer opened in a browser stays its own for pacing and the key
rule. press_key takes element_token, as the one allowed key needs. Chrome's
arguments are read only for its own processes, and stale comments, dead
fields and docs are aligned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on e8ab40a. This reviews the delta e8ab40a..48a700e: 1 commit, 9 files, +138/−77. The base is main, merge base b4cea4c. I loaded the helper skills (coderbook, ponytail-review, karpathy-guidelines) and ran the review in a separate session.

Previous findings

The previous review (e8ab40a) had no findings, so nothing carries over.

Findings

None.

Verified as fine

  • Several Computer windows per browser (sharedWindow.Windows, agent_browser.go:571-603):
    • setSharedWindow starts a new record when the pid changes. It dedups and appends, and works on a clone, so callers that already hold the old slice are unaffected.
    • Every reader was updated: isAgentChrome (agent_chrome.go:434), agentChromeKeys (via latest()), userChromeKeys (slices.Contains), userChromeWindow and Safari's launchAgentSafari.
    • TestSharedWindowsKeepEveryWindowOfTheBrowser covers the restart reset and keeps each browser's windows separate.
  • everydayChromeWindow ambiguity case. Because the len(matching) == 1 case comes first, the new condition len(fresh) > 1 && (settled || deadline) means the same as the old two-part condition, only simpler. The new timeout message names the site and tells the Agent to have the window closed.
  • everydayChromePID. It now runs ps -p only for processes whose comm ends with the Chrome main executable, instead of reading every process's command line. Helper processes have a different executable path, so they are filtered out before the second ps. A process that exits between the two reads is skipped.
  • Script guard:
    • launches now refuses launch, launchApp and call("launch_app") for every script, paced or not, which replaces startsChrome.
    • The run_script description (cua_script.go:22) and the README were updated to match.
    • No built-in Skill script calls launch from a script (checked with git grep).
    • Keys whose arguments carry their own pid/app/target/window_id are refused while the user's Chrome runs. A plain {role, name} target still goes through, and the new guard tests cover both.
  • Address bar lookup. Dropping AddressLabel for the everyday Chrome matches the stated design: the bar is found by role and place, so it works in any language.
  • press_key schema. press_key now lists element_token. That matches the one key that may go to the Computer's window, which must be bound to an element.
  • Skill version. humanlike-browse/1.0.5 is new in this PR and not yet on main, so editing it in place does not break Skill immutability.
  • Docs. The README (apps/computer/local-daemon/README.md) was updated with the multi-window and script rules in the same commit.

Process notes

  • Commit 48a700e is authored by jeffrey@simplexai.com, which is not linked to a GitHub account.
  • Merge state is BLOCKED (checks and review), not BEHIND.

CI

  • check (desktop) fails in apps/desktop/src/main/computer-daemon.test.ts > spawnDaemon > reports the last stderr line as the exit reason (expected '' to contain 'starting\n'). This PR touches nothing under apps/desktop, so the failure looks unrelated or flaky. Re-run it.
  • check (teamhub) was still pending. Every other check passed, including check (computer-go) and repository-gate.
  • I ran the touched tests locally on Linux with go test -count=1 -p 4 -run 'SharedWindows|ScriptGuard|EverydayChrome|AgentBrowser' ./internal/linger/localtools/, and they passed. The macOS-only integration tests (for example TestLocalToolCuaMacEverydayChromeOpensTheComputersWindowAndTakesNoKeys and ...RefusesTwoWindowsOfTheSite) skip on Linux, and they skip in CI's Ubuntu computer-go job too. They run only in the author's local macOS run.

Automated review by Claude Code


Automated review of 48a700e by Claude Code.

…reads

Every driver call fenced off the reads before it, the Agent's own
get_window_state included. A token from the read the Agent had just made
was then behind the fence, so the tab check before its next input staled
it: a bound Return, scroll or click failed as stale_element_token. Only a
call that may change the page fences now; reads fence nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 minor)

Scope: follow-up review on 48a700e. The new change is 1 commit (5a7dc5d) and 3 files under apps/computer/local-daemon/internal/linger/localtools/. The base is main with merge base b4cea4c, and the PR has 8 commits in total. Helper skills coderbook, ponytail-review and karpathy-guidelines were not loaded; I reviewed from the skill's checklist alone. The review ran in a separate session.

Previous findings

The previous review (48a700e) reported no findings, so there is nothing to track.

Findings

Minor

  • apps/computer/local-daemon/internal/linger/localtools/cua_snapshots.go:168: readTools is a second list of read-only Cua tools. cuaReadTools already exists at apps/computer/local-daemon/internal/linger/localtools/cua.go:565.
    • The two sets differ only in clipboard_read (only in readTools) and in checkPermissionsTool/report_blocker (only in cuaReadTools). call() returns before sent for those last two, so the difference doesn't matter there.
    • Failure scenario: a read tool added later to cuaReadTools but not to readTools will fence again. The Agent's tokens from that read then go stale on the next tab check, which is the same stale_element_token bug this commit fixes.
    • Fix: drop readTools and test cuaReadTools[name] || name == "clipboard_read" in sent, or add clipboard_read to cuaReadTools if it belongs there.

Verified as fine

  • The fix itself: observe runs inside sendInput, so it ran before sent(). Before this commit, the Agent's own get_window_state therefore fenced off its own snapshot. Skipping the fence for read tools fixes this at the root.
    • Inputs, run_script, launch_app and any tool not on the list still fence, so the default is the conservative one.
    • run_script still clears current in observe, so a script's own reads still can't carry a token.
  • Test: the updated TestSnapshotRowsCarriesTokensOverTheComputersOwnReads adds a sent("get_window_state") step and checks that s00000004:4 carries to s00000005:4. Without the fix this assertion fails.
    • The later steps were renumbered and still cover the cases where nothing may carry: a read before an input, and a reindexed diff.
  • Scope: the change is confined to the token-carry logic it describes, and nothing unrelated rides along. The commit message follows the conventions.

Process notes

  • The author of commit 5a7dc5d (jeffrey@simplexai.com) is not linked to a GitHub account.
  • Merge state is BLOCKED while required checks are still running.

CI

  • Passed so far: CodeQL, Analyze (actions), changes, and the check jobs for contracts, installer, teamhub-cli, web and packages.
  • Still running at review time: check (computer-go), check (desktop), check (teamhub) and repository-gate.
  • Run locally: go test -count=1 -p 4 ./internal/linger/localtools/ (Linux) passed in 25.5s.

Automated review by Claude Code


Automated review of 5a7dc5d by Claude Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on 5a7dc5d. Base main, merge base b4cea4c. 9 commits; 1 commit / 1 file changed since the previous review (7abd74c, cua_snapshots.go). Helper skills were not re-loaded for this one-file delta. The review ran in a separate session.

Previous findings

  • Minor: readTools duplicated cuaReadTools. Resolved. cua_snapshots.go:159 now checks cuaReadTools[name], and the separate readTools map is gone, so one list now decides which tools count as reads.

Findings

None.

Verified as fine

  • Effect of switching to cuaReadTools (cua.go:565):
    • check_permissions and report_blocker no longer fence snapshots. That is correct, because neither one sends input.
    • clipboard_read is not in cuaReadTools, so it now fences. This is the conservative direction. A token from before a clipboard_read is not carried over the Computer's own reads, so Cua rejects it as stale and the Agent reads the window again. A token is never moved onto a row that changed, so this costs at most one extra read.
  • sent is still called only after sendInput (cua.go:546). remap still runs before it.
  • Ran go test -count=1 -p 4 -run 'Snapshot|Lane|Read' ./internal/linger/localtools/. It passed in 10.2s.

Process notes

  • The merge state is BLOCKED while checks are pending and the PR has no approval yet.

CI

At the time of review, CodeQL, changes, and the web, teamhub-cli, packages, installer and contracts checks had passed. Still pending: check (computer-go), check (teamhub), check (desktop), repository-gate and Analyze (actions). I ran the localtools tests listed above myself, and they passed.

Automated review by Claude Code


Automated review of 7abd74c by Claude Code.

Cua refuses a background key while its app has another window
(same_pid_keyboard_ambiguity), and the user's everyday Chrome always has
one: on the bench a bound Return in the Computer's Xiaohongshu window was
refused that way. The one key the rule let through never arrived and only
cost the Agent a turn, so every key to that Chrome is refused now, and the
tool guide, README and humanlike-browse 1.0.5 say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 P3)

Scope: follow-up on 7abd74c. Base main, merge-base b4cea4c; reviewed the one new commit (64ead42, 6 files, +38/−108) against the previous review head. Helper skills: coderbook loaded; ponytail-review and karpathy-guidelines applied from their checklists. Ran in a separate session.

Previous findings

The previous review (7abd74c) had no findings, so nothing carries over.

Findings

P3

  • apps/computer/local-daemon/internal/linger/localtools/cua_script.go:73-78: the guardedScript doc comment still says "even the one key a tool call may send there (see everydayChromeKeys) is refused". This commit deleted everydayChromeKeys and the one-key exception, so the comment now points at a function that doesn't exist and describes an exception that no longer exists. Fix: drop the "a script cannot prove which window… (see everydayChromeKeys)" clause, or say that the script guard applies the same rule as tool calls (no key reaches the user's Chrome).
  • Minor, same area: apps/computer/local-daemon/README.md:355 is a much longer line than the paragraph's wrap width. It's cosmetic, so wrap it if you're touching the comment anyway.

Verified as fine

  • userChromeKeys is simpler now: a key tool or paged read whose effective pid (from the read token's window, or else the named pid) is the user's Chrome is always refused with COMPUTER_KEYS_REFUSED. A key with no pid is still refused with COMPUTER_TARGET_REQUIRED while that Chrome runs. Other apps are unaffected.
  • userChromePID replaces userChromeWindow. The per-browser sharedWindows lookup it dropped was only used for the deleted own-window exception. sharedWindows is still used by launch (agent_browser.go:200,596), so nothing became dead.
  • The deletion pass is complete: everydayChromeKeys, its table test and the name/paged parameters are gone, with no remaining callers. slices/strings still compile (GOOS=darwin go vet ./internal/linger/localtools/ passes).
  • The integration test now lists the bound Return to the Computer's window in the refused set and drops the "Return bound to a field" success assertion. set_value on that window is still asserted to pass.
  • MacToolGuide, the README and humanlike-browse 1.0.5 now all say the same thing: no key in any window, not even Return in a search box. 1.0.5 is still new in this PR, so editing it in place doesn't break the immutability rule. The PR body matches this commit.
  • The commit message gives the root cause (same_pid_keyboard_ambiguity in Cua always triggers because the user's Chrome has other windows). Removing the exception fixes that root cause rather than working around it.

Process notes

  • The branch is 10 commits behind main, and main requires up-to-date branches, so it needs a rebase.
  • The commit author jeffrey@simplexai.com isn't linked to a GitHub account.

CI

  • Passed so far: CodeQL, web, contracts, installer, packages, teamhub-cli, changes.
  • Still pending at review time: computer-go, teamhub, desktop, repository-gate, Analyze (actions).
  • I ran go test -count=1 -p 4 ./internal/linger/localtools/ -run 'EverydayChrome|Script|SharedWindow' on Linux, and it passed. The Mac-only Cua paths aren't exercised on Linux, and CI's computer-go job runs without -race. This commit adds no concurrency.

Automated review by Claude Code


Automated review of 64ead42 by Claude Code.

… exception

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on 64ead42, reviewing head 6f957f8 against base main (merge base b4cea4c, 11 commits on the PR). The delta since the previous review is 1 commit touching 2 files. Loaded the linger-pr-review instructions; coderbook, ponytail-review and karpathy-guidelines were applied from the review checklist. The review ran in a separate session.

Previous findings

  • P3: the script guard comment referenced the deleted everydayChromeKeys. Resolved. apps/computer/local-daemon/internal/linger/localtools/cua_script.go:76 now reads "This is the rule tool calls follow: no key reaches the user's Chrome." git grep everydayChromeKeys now finds only the live everydayChromeKeysRefused constant (agent_browser.go:664) and its uses.

Findings

None.

Verified as fine

  • The 64ead42..6f957f8 delta has no code changes. It touches only the guard's doc comment and a reflowed paragraph in apps/computer/local-daemon/README.md. The README wording is unchanged apart from line wrapping.
  • The updated comment matches the guard's behavior: guardedScript throws everydayChromeKeysRefused for every key to that Chrome (cua_script.go:111), which is the same rule as the tool-call path (agent_browser.go:655).
  • The commit message (docs(computer): ...) follows the conventions.

Process notes

  • The branch is 10 commits behind main, and main requires up-to-date branches, so it needs a rebase before merge.
  • The commits' author email jeffrey@simplexai.com is not linked to a GitHub account.

CI

  • At review time, CI was pending and CodeQL / Analyze (actions) was in progress. No results yet.
  • I ran no tests myself: the delta only changes a comment and docs.

Automated review by Claude Code


Automated review of 6f957f8 by Claude Code.

Cua's app.getState captures a window screenshot on every read unless
told not to. On the bench those reads took 6 to 17 s each on Taobao and
Xiaohongshu pages, against under 1 s for the same tree without one, and
made up a quarter to a third of each run. A script returns no screenshot,
so the guard now passes include_screenshot: false unless the script asks
for one. The script API names waitFor's 10 s limit, and humanlike-browse
1.0.5 says a screenshot read costs seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… as 1.0.6

An environment that installed 1.0.5 sealed it, so TeamHub refused to start
once the same version's SKILL.md changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 P2, 1 P3)

Scope: follow-up on 6f957f8 (previous verdict: Approve, no findings). Base main, merge base b4cea4c; 13 commits / 16 files on the PR, of which 2 new commits (b9de9fc, d486758) and 5 files were reviewed in depth here. Helper skills (coderbook, ponytail-review, karpathy-guidelines) were applied from their prescribed sections; the review ran alone in a separate session.

Previous findings

The previous review at 6f957f8 had no findings, so nothing to track.

Findings

P2

skills/builtin/humanlike-browse/1.0.5/SKILL.md:14: 1.0.5 goes back to the retired restart-by-consent flow and would land on main with it

d486758 restores 1.0.5 to its first text so that a bench environment that already sealed 1.0.5 can start TeamHub again. But 1.0.5 has never been on main: git ls-tree origin/main skills/builtin/humanlike-browse/ stops at 1.0.4, and the merge-base diff adds 1.0.5 as a new file. So after this merge, the repository ships a new immutable version that tells the Agent: "call launch_app again with restart_browser: true". The daemon has no such parameter: git grep restart_browser -- apps finds nothing. Line 34 also still allows "Return bound to the search box", which the daemon now refuses. Because the catalog keeps the version, it can never be fixed in place later.

What goes wrong: in any environment that installed 1.0.5 (the bench one), and anywhere a binding stays on 1.0.5, the Agent calls launch_app with a parameter that does not exist. It never asks the user to quit Chrome, so it loops or fails on COMPUTER_BROWSER_RESTART_REQUIRED. The fix for a dev database's sealed row has become permanent product content.

Fix: drop skills/builtin/humanlike-browse/1.0.5/ and its manifest entry from catalog.yaml, so the PR adds only 1.0.6, and reset or migrate the bench database instead. The immutability check allows this, since 1.0.5 is not in the merge-base tree. If 1.0.5 must stay, at least say in the PR why a never-published version needs to be kept.

P3

PR description, Summary: "humanlike-browse 1.0.5 carries the new route, the question to quit Chrome, and how to work without keys"

Since d486758, 1.0.6 carries these and 1.0.5 carries the old consent-restart text. Please update the description. The b9de9fc change (scripts read without a screenshot by default) is also not mentioned there.

Verified as fine

  • cua_script.go getState injection: include_screenshot: false is added only when the first argument is missing, undefined, or an object without include_screenshot. An explicit include_screenshot: true passes through. Caller fields keep priority ({include_screenshot: false, ...c[0]}), and the argument goes through plain() before the spread, so a getter on a caller object cannot reach the driver. The default applies to every script, not only the Computer's browser, which matches the description text ("It takes no screenshot ... unless you pass include_screenshot: true").
  • The tool description and the guard comment match the behavior. The new waitFor "at most 10000 ms" text is documentation only.
  • The new guard test case read without a screenshot covers no argument, a query object, and an explicit true. I ran it locally in its JS engine and it passed.
  • 1.0.6 SKILL.md is the earlier reviewed 1.0.5 text (at 6f957f8) plus one line about screenshot cost. The catalog lists 1.0.6 last and as the bootstrap Manifest, as builtin_catalog_test.go expects.

Process notes

  • main has 13 commits past the merge base, and main requires up-to-date branches, so a rebase is likely needed before merge.
  • All commits are by jeffrey <jeffrey@simplexai.com>.

CI

Passed: Analyze, CodeQL, changes, check (contracts / installer / packages / teamhub-cli / web). Still pending at review time: check (computer-go), check (teamhub), check (desktop), repository-gate. Ran locally: go test -count=1 -run TestScriptGuard ./internal/linger/localtools/ passed. No -race run was needed: the new commits change no concurrency.

Automated review by Claude Code


Automated review of d486758 by Claude Code.

1.0.5 never reached main, so the PR adds one new version instead of
freezing a retired text. An environment that sealed 1.0.5 keeps the row
and moves its latest Manifest to 1.0.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (0 new findings; 1 previous P3 still open)

Scope: follow-up on d486758. Base main, merge base b4cea4c, 14 commits; the delta since the previous review is one commit (f79692f) touching 2 files (skills/builtin/catalog.yaml, skills/builtin/humanlike-browse/1.0.5/SKILL.md deleted). Following the follow-up rules, only that delta was reviewed for new problems. The review followed the linger-pr-review checklist, which draws on coderbook, ponytail-review and karpathy-guidelines, and ran by itself in a separate session.

Previous findings

  • P2: humanlike-browse/1.0.5 brought back the retired restart flow: Resolved. skills/builtin/humanlike-browse/1.0.5/ is deleted, and its manifest entry is gone from skills/builtin/catalog.yaml. The catalog now lists 1.0.6 (the bootstrap manifest) and then 1.0.4. git grep restart_browser finds nothing in the repo. 1.0.5 was never on main, so deleting it does not break the immutability rule for built-in Skills.
  • P3: the PR description still says 1.0.5 carries the new flow: Not resolved. The description still says "humanlike-browse 1.0.5 carries the new route, the question to quit Chrome, and how to work without keys". The PR now ships only 1.0.6. Change that line to 1.0.6.

Findings

None in the new changes.

Verified as fine

  • The catalog for simplexai_humanlike_browse is consistent. bootstrap_manifest_id is skill_manifest_simplexai_humanlike_browse_1_0_6, and the manifests are 1.0.6 and then 1.0.0–1.0.4, which matches the directories on disk. No code or test outside the Skill tree references the 1.0.5 manifest ID or path.
  • Skipping version 1.0.5 does not trip catalog validation. apps/teamhub go test ./internal/skill/... passes locally.
  • 1.0.6 has no restart_browser instruction, which matches the Daemon's launch_app schema.

Process notes

  • Update the PR description (the 1.0.5 → 1.0.6 line above). The Validation section's note that the live runs used the earlier restart-by-consent flow is accurate as written.

CI

At review time, changes, check (contracts), check (installer), check (packages), check (teamhub-cli) and check (web) pass. check (computer-go), check (teamhub), check (desktop), repository-gate and CodeQL Analyze (actions) are still pending. The web test shards and web-static are skipped. Merge state is BLOCKED, presumably by the pending required checks and review.

I ran go test -count=1 -p 4 ./internal/skill/... in apps/teamhub, and it passed. The delta touches no Daemon Go code, so I did not rerun the Daemon's -race tests.

Automated review by Claude Code


Automated review of f79692f by Claude Code.

The first input after launch_app compared the front tab's title with an
empty one, so a click that changed nothing reported a new front tab,
and a second launch_app kept the earlier window's title. Each launch
path now stores the title with the tab count; the everyday Chrome path
reads it once, since its window search keeps no title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on d8f5651, reviewing head 964052f against base main (merge base b4cea4c). One new commit, 4 files (+17/−9). Loaded coderbook, ponytail-review and karpathy-guidelines. This review ran in a separate session.

Previous findings

  • P2: the first input after launch_app falsely reported a change to the front tab's title. Resolved. All three launch paths now store chromeTitle together with chromeTabs: agent_chrome.go:198-200 (separate profile), agent_browser.go:250-252 (Safari) and agent_browser.go:416-420 (everyday Chrome). TestLocalToolCuaMacEverydayChromeOpensTheComputersWindowAndTakesNoKeys (localtools_test.go:1991-1998) now checks that the title is Tab 0 after launch_app and that the first set_value adds no "tab in front" note. Without the fix, chromeTitle would be "", so recordAgentChromeTabs (agent_chrome.go:742) would add the note and the test would fail.

Findings

None.

Verified as fine

  • In the separate-profile and Safari paths, agentChromeTabs became agentChromeTabsAndTitle, which uses the same single get_window_state read. The tab count is unchanged and those paths make no extra read.
  • The everyday Chrome path makes one more tab-strip read after everydayChromeWindow, because that search does not keep the title. This happens once per launch_app and is not on the user message → provider_ready path.
  • If that read fails, the title stays "". The only effect is the old one-off note, so it does not break anything. chromeTabs still comes from the window search, so the tab-change guard behaves as before.
  • The operation.chromeTitle doc comment (manager.go:235) matches the new behavior.

Process notes

  • 964052f fix(computer): keep the front tab title from launch_app only patches d8f5651 from this same PR. main takes rebase merges, so squash it into d8f5651 (git rebase -i --autosquash).
  • None of the PR's commits has an author linked to a GitHub account.
  • The PR has no merge commits.

CI

  • At review time, check (computer-go), check (teamhub), check (desktop), repository-gate and Analyze (actions) were still pending. check (web), changes and CodeQL passed. Merge state is BLOCKED while these checks are pending.
  • I ran go test -count=1 -p 4 ./internal/linger/localtools/ -run 'EverydayChrome|AgentChrome|Safari' locally, without -race. It passed. This change adds no concurrency, so I skipped a race run.

Automated review by Claude Code


Automated review of 964052f by Claude Code.

Each input to the Computer's own browser cost about 2.2 s beyond the
driver call: a read of the whole page before it and at least two after
it, only to count tabs. Now:

- Before an input, the strip is read only when the window's title in
  list_windows differs from the front tab's title after the last input.
- After an input, the reads stop at the strip's depth, unless a script
  returned reads the Agent may act on, and a script that sent no input
  reads nothing, so the tokens its queries returned stay current.
- A role and name target reads up to 6000 elements, as the tab reads
  do, so a large shop page is no longer refused as read only in part.

On Taobao a click now takes about 1.3 s instead of 3.4 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 P2)

Scope: follow-up on 964052f (previous verdict: approve, no findings). Base main, merge base b4cea4c; 23 commits in the PR. Re-reviewed only the new commit 5359bbf (6 files: README.md, agent_browser.go, agent_chrome.go, agent_chrome_pace.go, cua.go, localtools_test.go). Applied the review checklists from the coderbook, ponytail-review and karpathy-guidelines skills (not loaded separately in this run). Ran in a separate session.

Previous findings

None. The previous review on 964052f had no findings.

Findings

P2

  • apps/computer/local-daemon/internal/linger/localtools/agent_chrome.go:735: when the window title from list_windows still matches chromeTitle, the pre-input tab check now returns early. A tab that someone else opens or closes behind the front tab of the Computer's window therefore no longer stops the task. That window lives in the user's everyday Chrome, so the user, or a delayed window.open from the page, can add a tab there.
    • Failure scenario: the Agent reads the page, then a background tab is added to the window (front title unchanged). The Agent clicks. checkAgentChromeTabs skips the read, and recordAgentChromeTabs sees got > before and returns "This input opened 1 new tab(s); the one in front shows …" (line 785, or the Close-button variant above it). The note blames the Agent's click for a tab it didn't open. It also tells the Agent to close a tab "to go back", but the tab in front is still the Agent's own page.
    • README.md:378 still says "A change in the number of tabs between the task's inputs stops the task with COMPUTER_TAB_CHANGED". That is no longer true for background tab changes.
    • Fix (small): reword the README invariant to say a tab change is caught before an input only when the front tab's title changed. In recordAgentChromeTabs, when front == title and the count changed, report the new count neutrally (for example "The window now has N tabs; the tab in front is unchanged") instead of saying the input opened or closed them. If the old guarantee still matters, run the bounded (agentChromeTabDepth) read before the input instead of skipping it. That read is still much cheaper than the old full read.

Verified as fine

  • Token carry-over is not harmed by the bounded reads. snapshots.sent fences every earlier read when an input is sent, so a depth-6 snapshot after an input cannot strand a token that would otherwise have carried over. When the title check does read before an input, it still reads the whole tree (depth 0), so remap keeps working.
  • When a script returned reads, recordAgentChromeTabs(..., full=true) keeps the post-input read whole, so the rows recorded from scriptResultReads stay in Cua's snapshot. A script that sent no inputs (scriptInputsSent known and 0) skips the tab read entirely. A script whose result has no call log, or that errored, still reads, which is the conservative choice.
  • settledAgentChromeTabs falls back to a full read when the bounded read finds no strip, and the settle loop reuses whichever depth worked.
  • If the titles differ for reasons other than a tab change (profile suffix, a page updating its own title), the code only loses the speed-up: it does the full read and refreshes chromeTitle.
  • scriptInputsSent comes out of countScriptInputs without a behavior change; the *[] pointer only adds the known flag.
  • locate's max_elements 3000→6000 matches the tab reads and is explained in the commit message.
  • README and PR description match the new read policy, apart from the invariant noted above.

Process notes

  • No merge commits and no fixup commits in the new range.
  • The new test TestLocalToolCuaMacAgentChromeReadsTheTabStripOnlyAsFarAsItNeeds is darwin-only. It skips on Linux CI, so its coverage comes only from local macOS runs.

CI

check (computer-go) was still pending at review time. web, teamhub, desktop, repository-gate, CodeQL, Analyze and changes pass; the rest are skipped. Locally: go test -count=1 -p 4 -run 'AgentChrome|Script' ./internal/linger/localtools/ passes on Linux (the darwin-only tests skip), and GOOS=darwin go vet ./internal/linger/localtools/ is clean.

Automated review by Claude Code


Automated review of 5359bbf by Claude Code.

The check before an input reads the tab strip only when the front tab's
title changed, so a tab someone opened or closed behind it no longer
stops the task. The result after the input then said the input opened
or closed that tab and told the Agent to close it to go back. When the
front tab is unchanged, the result now reports the count alone, and the
README states which tab changes stop a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on 5359bbf, reviewing the one new commit 65a2e02 (3 files: README.md, agent_chrome.go, localtools_test.go) against base main. The PR has 24 commits in total. Loaded coderbook (sections 2, 3, 5); ponytail-review and karpathy-guidelines were applied from their known checklists. Ran in a separate session.

Previous findings

  • P2: a tab opened or closed behind the front tab is no longer blocked and was reported as the task's own. Resolved. recordAgentChromeTabs (apps/computer/local-daemon/internal/linger/localtools/agent_chrome.go:780) now checks got != before && front == title first. When the front tab is unchanged, the note gives the new and old counts and the front title, and no longer says "This input opened/closed". The README (apps/computer/local-daemon/README.md:378) now says the check stops the task only when the front tab changed, and that a tab change behind the front tab is reported neutrally. The new test step in TestLocalToolCuaMacAgentChromeReadsTheTabStripOnlyAsFarAsItNeeds checks for the neutral note and checks that "This input closed" is absent.

Findings

None.

Verified as fine

  • The new case comes before the got > before && close != "" case. That case still applies whenever the front tab changed, which is the usual result when the Agent's own click opens a tab, so the Close-button hint is kept on the common path.
  • When the Agent's own input closes or opens a tab behind the front tab, for example through a background tab's Close button, it now gets the neutral count note. The note is still accurate, and the Agent can tell what happened from it.
  • c.op.chromeTabs and c.op.chromeTitle are still updated under c.op.mu before the switch. The change adds no new locking or state.
  • The README text matches the code in checkAgentChromeTabs, which skips the strip read when the window title equals the recorded front title.
  • The change stays within the previous finding; nothing unrelated came along with it.

Process notes

  • The branch is 38 commits behind main, and main requires branches to be up to date, so it needs a rebase.
  • No merge commits, and no fixup commits that should be squashed.

CI

check (web), CodeQL and changes pass. check (computer-go), check (teamhub), check (desktop), repository-gate and Analyze (actions) were still pending when this review ran. Locally I ran go test -count=1 -run TestLocalToolCuaMacAgentChrome ./internal/linger/localtools/ on Linux. It passed, but the changed test skips on Linux ("the Computer's own Chrome is macOS only"), so this run did not check the new assertion. If check (computer-go) runs on Linux, it will not check it either; it needs a macOS run.

Automated review by Claude Code


Automated review of 65a2e02 by Claude Code.

A bounded tab strip read that shows fewer tabs than before is read again
in full, since a strip split across the depth bound would read as fewer.
launch_app takes the front tab's title from list_windows instead of a
second whole read. A role and name target whose name is only white space
is refused instead of matching an element with no value. The note on a
press to a row that lists no press says a repeat by x,y waits for a read
that shows no effect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
linger-review[bot]
linger-review Bot previously approved these changes Nov 12, 2026

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on 65a2e02. Base main, merge base b4cea4c; 25 commits / 25 files in the PR. This pass reviewed only the 1 new commit (4762974, 5 files, +34/−18). Loaded the guidance from coderbook, ponytail-review and karpathy-guidelines. Ran in a separate session.

Previous findings

The previous review (65a2e02) had no findings, so there is nothing to carry over.

Findings

None in the new changes.

Verified as fine

  • agent_chrome.go:814: settledAgentChromeTabs now gets before. If a bounded read shows fewer tabs than before, it reads the strip again in full. This catches a strip that was split across the depth bound. A bounded read can't show more tabs than are really there, so the got > before case correctly skips the second read. The settle loop then keeps depth = 0. The test expects click, 6, 0, 0: one bounded read, one full read, then one full settle read that agrees. That matches the code path.
  • agent_browser.go:420: launch_app now takes chromeTitle from list_windows (window.Title) and no longer does a full strip read. The pre-input check (checkAgentChromeTabs) compares the window title against this same list_windows title. The strip read's title is window_title from get_window_state, so both sides come from the same kind of source. If the title changes, the next input does one full read, as the new comment says. The fixture now gives each window its front tab's title, so the existing Tab 0 assertion covers this path.
  • cua.go:1323: sameSpaces(label) now runs before the empty-target check. A name made only of white space (including a no-break space) now returns COMPUTER_TARGET_REQUIRED instead of matching an element with no label or value. The new test sends "  ". The later comparison loop keeps working on the normalized label, and nothing was left behind from the moved line.
  • cua.go:537: the click note now says a repeat by x,y should wait for a read that shows no effect. The README (README.md:384-395) and the test assertion were updated to match, and the PR body describes the same behavior.
  • No new env vars, contracts or cross-app imports.

Process notes

  • 964052f, 65a2e02 and 4762974 are fix(computer) commits that only correct behavior added earlier in this PR (d8f5651, 5359bbf). Because main takes rebase merges, consider folding them into those commits (git rebase -i --autosquash). This doesn't block the merge.
  • No merge commits. The commit author is linked (jeffrey). The merge state is BLOCKED because the review is still required and checks are pending.

CI

  • Pending: check (computer-go), check (teamhub), check (desktop) and repository-gate. Passed: check (web), changes, CodeQL and Analyze (actions). The other checks were skipped by the path filter.
  • Every test that touches this change (TestLocalToolCuaMac*) skips off macOS. check (computer-go) runs on ubuntu-latest, so even a green CI does not exercise these paths. The only evidence they pass is the author's macOS run.
  • Locally on Linux, the four touched tests skipped as expected, and GOOS=darwin go vet ./internal/linger/localtools/ is clean.

Automated review by Claude Code


Automated review of 4762974 by Claude Code.

Each window a task opens with launch_app keeps its own tab count and front
title, so going back to an earlier window keeps its tab check. The script
guard drops a read that started before an input finished, such as one made
during the pause of an input the script did not await, so stale rows are
not carried. The everyday Chrome window search restarts its settle wait
when the lone candidate window changes or leaves. The picture viewer test
fixture and the countTabs wrapper, left without callers, are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 P3)

Follow-up review on 4762974 → 87e692b. Scope: base main, merge base b4cea4c, 26 commits in the PR; this pass reviewed the one new commit (87e692b, 8 files). The coderbook, ponytail-review and karpathy-guidelines skills were loaded. This review ran in a separate session.

Previous findings

The previous review (4762974) had no findings.

Findings

P3

  • apps/computer/local-daemon/internal/linger/localtools/agent_browser.go:453 — everydayChromeWindow now restarts the settle wait when the lone new window leaves or another window takes its place (the candidate switch). No test covers this. If the code went back to setting seen only once, the old bug would come back: a short-lived window (A) starts the clock, a different window (B) then appears, and B is taken before it has stood for everydayChromeSettle, and no test would fail. Fix: add one case to the existing everyday-Chrome window tests where the window list shows A, then only B, and assert that B is returned only after the settle time counted from when B appeared.

Verified as fine

  • holdAgentChromeWindow / agentChromeTabWindow: each window opened with launch_app keeps its own heldTabs. An input that names no window goes to the latest window. A named window that is not in the map is not held, which matches the old rule that only the task's own window is held. A new pid resets the map. All map access happens under op.mu. The map is non-nil whenever ok is true, so the writes in checkAgentChromeTabs and recordAgentChromeTabs cannot hit a nil map. Moving the window resolution out of settledAgentChromeTabs into its two callers keeps the same behavior.
  • Script guard fence (cua_script.go): an input fences reads when it starts, when it settles and when it rejects. A read is kept only if inputs did not change between its start and its result. I traced an unawaited input across its paced pause: a read that overlaps the input's run is dropped, and a read that finishes before the run is cleared by the fence. The missed handler re-throws, so rejection behavior is unchanged. input(k, c) is computed once before the launch and key checks, and it is a pure check.
  • The dead countTabs wrapper and the unused picture-viewer test fixture were removed (deletion pass), and the README was updated to describe per-window holds and the read fence.
  • Ran locally: go test -race -count=1 -p 4 ./internal/linger/localtools/ -run 'AgentChrome|ScriptGuard|EverydayChrome|Tabs' passed. With -v, TestAgentChromeHoldsEachWindowTheTaskOpened and TestScriptGuardDropsAReadThatAnInputOverlaps both run and pass; neither is skipped.

Process notes

  • The branch is 53 commits behind main, and main requires up-to-date branches, so it needs a rebase.
  • The commit author jeffrey@simplexai.com is not linked to a GitHub account.
  • 87e692b and several earlier fix(computer) commits patch behavior added earlier in this same PR. Consider squashing them into the commits they fix before the rebase merge.

CI

check (computer-go) was still pending at review time. check (desktop), check (teamhub), check (web), repository-gate, CodeQL and Analyze passed; the other jobs were skipped. CI runs computer-go without -race; the focused -race run above covers the concurrency-relevant tests changed in this commit.

Automated review by Claude Code


Automated review of 87e692b by Claude Code.

… task

A tab that the task's own input opened after the reads that followed it
looks the same as one someone else opened, and ending the task on it threw
away a task that was working. The check before an input now refuses only
that input, holds the window to the tabs it shows now, and asks the Agent
to read the window and go on only when the front tab is its page. The
humanlike-browse Skill says so and ships as 1.0.8, since an environment
sealed 1.0.7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 P2, 1 P3)

Scope: follow-up review of 87e692b → a3fadde (1 new commit, 5 files), against base main at merge-base b4cea4c, 27 commits on the PR in total. Loaded the review rubric inline (coderbook / ponytail-review / karpathy-guidelines sections as specified). Ran in a separate session.

Previous findings

  • P3 agent_browser.go:453: the new branch that restarts the wait when the only new window disappears or is replaced still has no test. Not resolved. Nothing changed in agent_browser.go or agent_browser_test.go since 87e692b.

Findings

P2

  • apps/computer/local-daemon/internal/linger/localtools/agent_chrome.go:764: after a tab-count change, the check now refuses one input and immediately re-holds the window to the new count and front title (c.op.chromeHeld[id] = heldTabs{count: got, title: front}). Nothing on the Daemon side makes the Agent read the window before the next input. The Computer's own strip read does not stale the Agent's tokens (cua_snapshots.go:23), so an immediate retry with the old element_token passes. The updated test does exactly that: it retries click with s1:1 and expects success, localtools_test.go:1097. An x,y click passes too.
    • Failure scenario: the user clicks a link in another app, and Chrome opens it in its most recently active window, which is the Computer's. The Agent's next input is refused once. If the Agent then retries, or clicks by x,y without the read the message asks for, the input lands on the user's page. Before this commit the task stopped at that point.
    • Why it matters: the safeguard has moved from a deterministic server-side stop to an instruction the model has to follow.
    • Fix: keep the "refuse once" behavior, but put a "needs read" mark on the held window when refusing. Clear the mark only when the Agent itself runs get_window_state on that window. While the mark is set, refuse inputs again with COMPUTER_TAB_CHANGED. Alternatively, stale the snapshot tokens that predate the refusal. Add a test showing that a retry without a read is refused and that a retry after a read goes through.

P3

  • apps/computer/local-daemon/internal/linger/localtools/localtools_test.go:1057: the test is still named TestLocalToolCuaMacAgentChromeStopsWhenSomeoneElseChangesTheTabs, but the task no longer stops.
    • The PR body still says "humanlike-browse 1.0.7 carries the new route", but the catalog now ships 1.0.8, and the body doesn't mention the refuse-once rule for COMPUTER_TAB_CHANGED.
    • The edited doc comment at agent_chrome.go:736 and the README paragraph at README.md:383 have an unwrapped long line.
    • Fix: rename the test (for example …RefusesOneInputWhen…), update the PR description, and rewrap those lines.

Verified as fine

  • chromeBlocked is no longer set for tab changes, but it is still used for reported checks (agent_chrome_pace.go:184,272). The old COMPUTER_SITE_BLOCKED assertion was removed, not left dead.
  • The refusal still returns errNotSent, and the test confirms that no fixture call was made (localtools_test.go:1091).
  • The re-hold after the refusal updates both count and title under op.mu, so the next input doesn't loop on the same change.
  • The Skill bump 1.0.7 → 1.0.8 is a rename of a version that never reached main (main has 1.0.0–1.0.4), so built-in Skill immutability holds. catalog.yaml updates both the bootstrap manifest and the manifest entry. The new Skill line matches the Daemon's error text.
  • README and Skill text are aligned with the code change.

Process notes

  • The branch is 53 commits behind main, and main requires up-to-date branches, so it needs a rebase.
  • main takes rebase merges, so every commit lands as is. Several commits patch earlier commits in this PR and should be squashed with git rebase -i --autosquash before merge. Examples: the Skill version churn d486758 / f79692f / 75872f5 / a3fadde, and a3fadde reverting the stop-the-task behavior added earlier.
  • No merge commits.

CI

  • Pending at review time: check (computer-go), check (teamhub), check (desktop), repository-gate.
  • Passed: check (web), CodeQL, changes.
  • I ran go test -race -count=1 -run 'TestLocalToolCuaMacAgentChrome(StopsWhenSomeoneElseChangesTheTabs|KeepsTabsItsScriptsChange)' on Linux. Both tests skip there ("the Computer's own Chrome is macOS only"). The same will happen on CI's Linux computer-go job, so the changed tab-check tests only run on macOS.

Automated review by Claude Code


Automated review of a3fadde by Claude Code.

The settle wait of the everyday Chrome window search is its own type, so a
test shows that a window that showed briefly lends no time to the window
that takes its place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (0 new findings; 1 P2 and 1 P3 from the previous review still open)

Scope: follow-up on a3fadde, reviewing a0f0f05. Base main. The new range is 1 commit touching 2 files (agent_browser.go, agent_browser_test.go). The PR has 28 commits since the merge base. Helper skills were not loaded in this run; I reviewed against the linger-pr-review checklist directly. The review ran in a separate session.

Previous findings

  • P2: after a tab change, the guard relies on the model to re-read the window (agent_chrome.go:764): Not resolved. agent_chrome.go has not changed since a3fadde. After one COMPUTER_TAB_CHANGED refusal, the held count is reset to what the window shows now (agent_chrome.go:765-767). The next input therefore goes through even if the Agent never read the window.
  • P3: a test name and the PR description are out of date: Not resolved. The test is still named TestLocalToolCuaMacAgentChromeStopsWhenSomeoneElseChangesTheTabs (localtools_test.go:1056), but the task no longer stops. The PR body still says humanlike-browse 1.0.7.

Findings

None in the new changes.

Verified as fine

  • windowSettle.settled (agent_browser.go:478-489) is a straight extraction of the inline switch it replaces. Its behavior is the same: it takes one now per poll where the old code called time.Now() up to three times, which only tightens the timing. The loop in everydayChromeWindow uses it in the same place.

  • TestWindowSettleCountsFromTheCandidate covers the four cases that matter:

    • a new candidate resets the clock;
    • a candidate settles after everydayChromeSettle;
    • a candidate that leaves resets the clock;
    • the same window coming back starts from zero.

    The test injects time, so it does not sleep. I traced each assertion against the switch.

  • The new code adds no new configuration, environment variables or contract changes.

Process notes

  • The merge state is BLOCKED: CI is still running and the PR needs approval.
  • There are no merge commits. The new commit is a test-plus-extraction commit with a conventional test(computer): header.

CI

At review time the computer-go, teamhub, desktop, web, repository-gate and CodeQL checks were still pending. changes passed, the rest were skipped, and nothing had failed.

What I ran locally: go test -count=1 -run 'TestWindowSettleCountsFromTheCandidate|TestAgentChrome' ./internal/linger/localtools/ passed. That run was on Linux, so the macOS-only Cua tests did not run. I did not run -race: the change adds no concurrency, because windowSettle is local to a single loop.

Automated review by Claude Code


Automated review of a0f0f05 by Claude Code.

A cancelled Start whose Codex process exited before the stop closed its
stdin was reported unconfirmed: the stop then counted the exit as its own,
and only an outside exit excused the cleanup error the gone process left.
A reaped leader and an empty group are the evidence that nothing runs the
turn, so a cleanup that failed because the process had gone is excused
whoever ended it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve with nits (1 P3)

Scope: follow-up on a0f0f05, reviewing 9c602e4 against main (merge base b4cea4c). 29 commits, 26 files in the PR; the incremental diff since the previous review is one commit touching apps/computer/local-daemon/internal/provider/native/codex_process.go. Ran as a separate session. I followed the linger-pr-review procedure (coderbook passes, ponytail-review sections 1–3, karpathy-guidelines sections 2–3) without loading those skill files separately.

Previous findings

The previous review (a0f0f05, approve with nits) recorded no findings, so there is nothing to carry over.

Findings

P3

  • apps/computer/local-daemon/internal/provider/native/codex_process.go:314: the fix has no deterministic test. A stop on a process that has already gone (errCodexChildGone) is now excused no matter who ended the process. The race in the commit message happens when the leader exits just before the stop closes stdin, so reap records external=false. Only TestCodexCancelledStartOnResidentKeepsHomeUsable covers it, and that test reaches the race only some of the time. The same commit also removes that test's 2 s cleanup budget, so the test now runs with the default budget. A unit test in codex_units_test.go would pin it down. Build a codexProcess with terminated=true, a reaped leader, an empty group and a transport failed with errCodexChildGone, then assert that stopTree() returns true. That test fails on a0f0f05.

Verified as fine

  • The new excused rule is safe. Confirmation still requires isReaped() && groupGone(), so an excused cleanup error never confirms a stop while anything in the process group is alive. Before this change, a daemon-caused exit returned unconfirmed once. A later retry then took the cleanupDone && reaped branch and confirmed on groupGone() alone. The fix gives that result on the first call instead of surfacing a spurious ExitUnconfirmed.
  • errors.Is still matches the wrapped form fmt.Errorf("%w: %w", errCodexChildGone, err) from codex_transport.go:313.
  • p.external is still read by alive(), externalExit() and exitDiagnostics(), so removing its read in stopTree leaves no dead field.
  • The comment explains the invariant, not history, as the repo's conventions require.

Process notes

  • The branch is 53 commits behind main, and main requires up-to-date branches, so it needs a rebase before merge.
  • c9c2fa2 ("give the cancelled-start cleanup its full budget") only adjusted the test's cleanup budget. 9c602e4 removes that adjustment again. Squash them so main doesn't get a test change and its revert. The Codex stop fix is also unrelated to the everyday-Chrome feature; a separate PR would have kept this one focused.
  • Commits are authored as jeffrey@simplexai.com, which is not linked to a GitHub account.

CI

All checks that ran pass: computer-go, teamhub, desktop, web, repository-gate, CodeQL. The other jobs were skipped by path filters. CI's check (computer-go) does not run with -race. I ran it locally:
go test -race -count=3 -p 4 -run 'TestCodexCancelledStartOnResidentKeepsHomeUsable|TestCodex.*Stop|TestCodex.*Exit|TestCodex.*Cleanup' ./internal/provider/native/ passed in 55.9 s.

Automated review by Claude Code


Automated review of 9c602e4 by Claude Code.

…he window

After a change of tabs refused an input, the next input went through even
when the Agent had not looked, since the Computer's own strip read does not
stale the Agent's token. The window now stays refused until the Agent's own
get_window_state of it. The Codex stop's cleanup excuse is its own function
with a deterministic test, and the tab-change test is named for what it
proves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@linger-review linger-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (0 findings)

Scope: follow-up on 9c602e4; base main, merge base b4cea4c; 30 commits in the PR, 1 new commit (7c54a58, 6 files) reviewed for new problems. Helper skills: coderbook, ponytail-review and karpathy-guidelines were not loaded in this headless run; I applied the review checklist from the review instructions instead. Ran in a separate session.

Previous findings

  • P3 No deterministic test for the Codex stop-confirmation fix: Resolved. The excuse is now its own predicate, codexCleanupExcused (internal/provider/native/codex_process.go:318), and TestCodexCleanupExcusedWhenTheProcessHadGone covers nil, a wrapped errCodexChildGone and an unrelated cleanup error. stopTree calls the predicate directly, so the test covers the decision the fix changed.

Findings

None.

Verified as fine

  • checkAgentChromeTabs now stores unread: true with the new count and title. The early held.unread return refuses every input to that window with COMPUTER_TAB_CHANGED wrapping errNotSent. A run_script aimed at the window goes through the same paced check, so a script can't slip an input in before the Agent's read. run_actions steps stop at the first refused step, before the batch's own read.
  • Only an Agent-visible tree read lifts the hold. readAgentChromeWindow runs from finishInput only for get_window_state without include_accessibility_tree: false. The permission-evidence capture (evidence.go:215) passes include_accessibility_tree: false, and the Daemon's own strip reads (agent_chrome.go, agent_browser.go) call c.client.request directly. Neither path clears the flag.
  • When a read has no window_id, pickFocusedWindow fills in a float64 window ID before finishInput runs. That ID matches chromeHeld keys exactly, so reading one of the user's other Chrome windows does not clear the hold on the Computer's window.
  • readAgentChromeWindow reads under one lock and writes under another. The gap is safe because every call holds desktopLane and chromeHeld belongs to one operation.
  • recordAgentChromeTabs and holdAgentChromeWindow both write a fresh heldTabs with unread false. That is right, because both run only after an input succeeded or a new launch_app.
  • The README paragraph and the error text now name get_window_state, matching the code. The test asserts both the second refusal and the release after a read.

Process notes

  • The branch is 53 commits behind main, and main requires up-to-date branches, so it needs a rebase before merge.

CI

  • At review time check (computer-go), check (teamhub), check (desktop) and repository-gate were still pending. CodeQL, changes and check (web) passed, and the rest were skipped as out of scope.
  • The tab-hold test (TestLocalToolCuaMacAgentChromeRefusesInputsWhenSomeoneElseChangesTheTabs) skips on non-darwin hosts, so neither Linux CI nor this host runs it. Only the author's macOS run covers it.
  • Ran locally: go test -count=1 -run TestCodexCleanupExcused ./internal/provider/native/ passed. I didn't run -race: the new code adds no goroutines, and the locking analysis above covers the state it touches.

Automated review by Claude Code


Automated review of 7c54a58 by Claude Code.

@jeffrey-bin
jeffrey-bin disabled auto-merge November 12, 2026 15:16
@jeffrey-bin
jeffrey-bin disabled auto-merge November 12, 2026 15:21
@jeffrey-bin
jeffrey-bin added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit edc9f5d Nov 12, 2026
14 checks passed
@jeffrey-bin
jeffrey-bin deleted the feat/humanlike-input-fidelity branch November 12, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant