镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Run native Codex through plugin execution targets - #2150

Merged
dr0uleau merged 96 commits into
mainfrom
nexus/build-generic-execution-support-for-native-buzz-33ea2bea
Nov 5, 2026
Merged

dr0uleau merged 96 commits into
mainfrom
nexus/build-generic-execution-support-for-native-buzz-33ea2bea

Conversation

@dr0uleau

@dr0uleau dr0uleau commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Why

Native Codex agents need to run on another computer. Plugins need to manage those targets while Buzz keeps identity keys and tool permissions in the app.

What

Add a plugin execution API and use it for native Codex. Each conversation keeps its target key across turns and app restarts. Start a new session requests a new target. Agents without a binding keep local execution.

How

The harness saves the session key before it opens a target. The plugin supplies process I/O, file reads, workspace resolution, and cleanup. Failed restore requires an explicit new session. Settings and model discovery do not allocate targets. Remote cards open the agent’s scoped model Settings. Activity reads saved remote records.

Buzz requires native custody and idle work before it saves a binding. Retirement waits for old work and cleanup. Failed cleanup blocks the change and permits a later retry. Durable reads stop a stale window from starting local work for a saved remote binding. Plugin grants and callback checks retain the existing authority boundaries.

Risk

Shared admission, storage, and Codex lifecycle code also serve local agents. Protected agents refuse remote bindings. Remote Codex usage totals, transcript views, media download, and HTTP MCP tunnels remain unavailable.

Testing

The signed macOS app at 2d0a01847 has the same production code and native build inputs as 21022ba298. Against a real relay, native Codex writes a file on its plugin target and reads the exact content with a tool. After a force-quit and reopen, it retains the session key, Codex thread, target, and workspace. A second turn reads and updates that file. Saved credential access works after restart.

With the checked adapter artifact c2642cad1ca8, the app resumes a suspended target. The first turn reports the lost connection. The app's Retry control then completes the same request in the same session, thread, target, and workspace. Native tool output and a separate file read confirm the exact result. The kernel boot changes, the saved launch journal records clean recovery, and the verified receipt is removed. An old receipt without launch proof stays blocked and intact.

Remote model cards open the correct agent settings in light and dark themes. The model choices use the target default or a custom ID. The controls also run at 900 and 1440 logical pixels and at 1.5 text scale. The operator sends a tool-read request in the app. It returns the exact content codex-recovery-7b2ad6d0-resume and leaves the file unchanged. Human acceptance passes.

No browser cases are added or removed. The full message-actions file passes 20 cases across Chromium and WebKit on Blox at 0b21e3ed3. Later changes preserve its production and browser source.

buzz-review-completed

Generated with Codex

dr0uleau and others added 8 commits October 30, 2026 15:55
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
@dr0uleau
dr0uleau requested review from gilgtm and kazemek21 November 2, 2026 16:17
@dr0uleau
dr0uleau marked this pull request as ready for review November 2, 2026 17:37
@dr0uleau
dr0uleau requested a review from a team as a code owner November 2, 2026 17:37
dr0uleau and others added 9 commits November 2, 2026 09:38
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>

* origin/nexus/build-generic-execution-support-for-native-buzz-33ea2bea:
  Guard execution settings during agent changes

Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
dr0uleau and others added 2 commits November 2, 2026 12:38
Upstream-commit: 4293f6d

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Kyle reviewed the PR and the Nexus review-tracker notes. Approved with three non-blocking comments.

— Posted by Codex (gpt-6.1-sol) on behalf of kylez

Comment thread src/features/agents/service.ts Resolved
Comment thread src/bundled/pi/runtime.ts Resolved
Comment thread src/bundled/pi/runtime.ts Resolved
dr0uleau and others added 5 commits November 2, 2026 14:27
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>

* origin/nexus/build-generic-execution-support-for-native-buzz-33ea2bea:
  Keep manual preference recovery under test control
  Wait for thread readiness before keyboard input

Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>

* origin/nexus/build-generic-execution-support-for-native-buzz-33ea2bea:
  Check retired execution through its process list
  Preserve execution choice and Pi lifetime

Signed-off-by: Daniel Rouleau <drouleau@squareup.com>

# Conflicts:
#	docs/buzz-help.md

@gilgtm gilgtm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 [P2] Full review of the current native Codex replacement found a failed-cleanup retry defect, detailed inline. The remote-card host-scope regression is discussed in the existing card thread. Source review only.

Posted by duel-review skill [driver: codex(high), mode: deep-dual, review synthesized by codex(high)]

Comment thread src/features/agents/execution.ts Resolved
dr0uleau and others added 13 commits November 4, 2026 13:28
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
The coordination browser test can report `animated: false` after a real
disclosure transition completes between frame samples. Main [run
41693502039](https://github.com/block/buzz-app/actions/runs/41693502039),
WebKit job 126119613338, failed this assertion with no geometry failures
at `589c2a489fe87ab45315359014d7d46d2df4a240`.

Observe the panel's `grid-template-rows` transitionstart event before
clicking, and remove the observer in `finally`. Keep the existing
frame-by-frame clipping, alignment, settlement, and keyboard assertions.
No product changes or browser cases added/removed. Browser coverage
remains necessary for actual CSS transitions and rendered geometry.

Validation:

- At the failing main commit, delaying only test sampling by 350ms
reproduced the exact failure while recorded transition events proved
motion occurred. The original hosted trace does not record those events,
so its precise scheduling cause remains unproven.
- The repaired test passes with the same diagnostic delayed sampler in
Chromium and WebKit (8.1s / 8.3s test execution; 34.5s local runner).
Diagnostic instrumentation is excluded from this change.
- Disabled-motion control still fails the animation assertion (WebKit),
so an absent transition remains detectable.
- Normal repaired test: 2/2 per engine, Chromium 3.7–3.9s and WebKit
4.4–4.7s; 30.1s local runner. Only the affected case ran, with no
retries.
- At commit `8a53027b3`, the affected test passes in Chromium (4.3s) and
WebKit (4.8s), 18.1s runner; head verified before and after.
- Independent agent review approved with no findings. The reviewer's
reduced-motion control fails in both engines as intended. The review run
also inadvertently included measurement dependencies.
- Hosted coordination repetition checks passed 10/10 per engine in [run
41701145383](https://github.com/block/buzz-app/actions/runs/41701145383).
Attempt 1 hit the separate held-pointer travel test defect
(`history-loading.spec.mjs:347`, expected ≥6 steps, got 2), already
repaired by unmerged #2250. A single-job hosted retry passed at the
unchanged repair head. Attempt 2 completed successfully at 19:37:26 UTC:
39 successful checks, two expected skips, none pending. This PR does not
repair the held-pointer defect.

No full suite invoked locally; hosted CI supplies broader validation.
Native acceptance and a real relay are outside this test-only change.

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
(cherry picked from commit bad2fec)
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
kazemek21 added a commit that referenced this pull request Nov 5, 2026
## TL;DR

Any agent running on this device can now read and change Buzz settings
when you ask it to, through generic `settings_list`, `settings_get` and
`settings_set` MCP tools. There is no per-agent switch and no UI change.
The feature that owns each setting defines it once in a shared catalog
and writes it through its own live setter, so the settings screen,
storage and other windows see the change as if you had made it.

## What agents can change

| Key | Values | Owner |
| --- | --- | --- |
| `appearance.mode` | `system`, `light`, `dark` |
`shared/theme/settings.ts` |
| `appearance.interface_size` | 80–200, steps of 10 (percent) | same |
| `appearance.accent` | the accent ids | same |
| `appearance.vibe` | `low` (Chillin’), `high` (Buzzin’) | same |
| `conversations.thread_placement` | `side`, `main` (new thread tabs
only) | `features/messages/thread-preferences.ts` |
| `conversations.follow_up_behavior` | `immediate`, `queue` (Solo) |
`features/messages/follow-up-preference.ts` |
| `conversations.autocorrect` | `true`, `false` (Mac message box) |
`features/messages/autocorrect-preference.ts` |
| `agents.remember_mentioned` | `true`, `false` |
`features/messages/mention-preferences.ts` |

`src/app/device-settings.ts` composes the catalog.
`createDeviceSettings` exposes one generic API (`list`, `get`, `set`)
over string-enum, integer and boolean settings. Adding a setting means
adding one definition, with no tool or router edits. Values must match
exactly, with no aliases, case folding or clamping. Unknown keys, wrong
types and extra arguments are refused, and nothing changes. Each write
is read back. When storage refuses it, the answer is `saved: false` with
the owner's own reason.

**Not covered yet:** Buzz has no shared settings store, so coverage
follows which settings already have a synchronous setter that fits this
shape.
- Notification preferences and keep-awake would each need an adapter,
and notifications would also need `services.ts` construction order
changed.
- Shortcuts take object values.
- Plugins' own settings, such as Focus, exist only inside the plugin.
- Excluded by design: actions such as sign-in, updates, leaving a
community and clearing caches, as well as secrets, grants and consent,
decision providers, models and anything destructive.

## Who gets the tools

- **Every agent run.** An agent run's handle carries the catalog, and
its Buzz MCP lists the three tools (`load: "search"`). Calls go through
the same `appClient` check and signal as every other mutating Buzz tool,
such as sending a message, and the write is synchronous after that
check. A removed agent, a changed connection, or a turn the harness has
ended or aborted changes nothing. Stop behaves as it does for those
tools in each harness. Where the MCP connection outlives a stopped turn
(Goose, and Claude Code without protection), a call already in flight
when you press Stop can still land, as a message send can.
- **Not plugins or external connections.** A plugin's outside-run
`Agents.handle` has no settings. Tools a plugin adds get the agent's
client without `settings`. External MCP connections use that non-run
handle and also get none.

## Revision

An earlier revision gated this behind a per-agent, off-by-default
**Change Buzz settings** switch in Agent details. It also added a
settings-only Stop check to the Claude Code, Codex, Goose and Pi
runtimes. Following product direction, both are removed. The
AgentManage, AgentDetails, AgentsPage and ProfilePanel files, the agent
record, and the four runtimes and Pi host are back to `main`. The
earlier screenshots, how-to-try steps and the runtime and permission
test evidence described that design and no longer apply.

## Overlapping work

This PR does not build on any of these and changes none of their
behavior:
- #2211: external auth and catalog.
- #1878 and #839 (now on main): Claude and Goose protection, whose
pre-tool checks still review a settings call.
- #2150 and #2186: execution target.
- #2180: themes. Theme selection is left out of the catalog.
- #1704: queued follow-ups. This PR only exposes the existing behavior
preference.

## Validation

Run on a Blox Linux workstation against this exact tree:

- `tsc --noEmit` and biome on the touched files pass.
- vitest passes for 10 files (922 tests, after rebasing onto #2280).
These are the changed tests, plus the Claude Code and Codex runtime
tests and `AgentManage`, now back to `main`:
- `shared/device-settings`: exact values, booleans, read-back, unsaved
and closing.
- `app/device-settings`: real `createAppearance` and the three message
and agent preferences in jsdom, with persistence, change events and a
refused `setItem`.
- `buzz-mcp`: the catalog through the router, listing by connection,
plugin tools without settings, and `appClient` checking the turn before
each call.
- `features/agents/service`: settings on every run's handle with no
switch, none on a plugin's handle, and an aborted turn signal or agent
removal while a call waits for review changing nothing.

No browser journeys were added or removed. Deferred: the native app
hasn't been run with a live agent turn. The steps below cover that.

## How to try it

1. Ask any agent running on this device to switch Buzz to dark mode. The
app should switch at once, and **Settings → Appearance** should show
Dark.
2. Ask it to open new threads in the main pane, then open a thread. It
should open in the main pane, while open threads stay put.
3. Ask it to queue Solo follow-ups. **Settings → Appearance → Follow-up
behavior** should show Queue.
4. Ask it for something outside the list, such as a theme. It should say
that setting isn't available, and nothing should change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
@dr0uleau
dr0uleau merged commit 21f6b97 into main Nov 5, 2026
42 checks passed
@dr0uleau
dr0uleau deleted the nexus/build-generic-execution-support-for-native-buzz-33ea2bea branch November 5, 2026 01:42
gilgtm added a commit that referenced this pull request Nov 5, 2026
…lled-read

* origin/main: (28 commits)
  Run native Codex through plugin execution targets (#2150)
  test: split Solo access browser scenarios (#2499)
  feat(agents): let any agent change Buzz settings through the MCP (#2248)
  Let plugins decorate host sidebar rows and add sort modes (#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (#2083)
  feat(agents): move version history into settings (#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (#2465)
  feat(plugins): let agents request plugin changes for owner review (#2280)
  fix(messages): keep the hover toolbar while its menu is open (#2439)
  feat(workflows): suggest channel members for @ in Send Message text (#2309)
  AIDA-2344: Exclude muted channels from Dock (#2313)
  feat(search): add slack-style message search operators on desktop (#2463)
  test: fix invitation and coordination checks on main (#2517)
  perf(search): name searched people from the profile search response (#2353)
  Use the merge queue for pull requests that require it (#2452)
  fix(browser): load scheme-less address input over https, falling back to http (#2467)
  fix(identity): stop one-click key unlinks and show the linked backed-up key (#2486)
  fix(test): observe coordination transition events (#2297)
  ...

Signed-off-by: Gil Birman <gil@squareup.com>
Co-authored-by: Claude <noreply@anthropic.com>

# Conflicts:
#	docs/buzz-help.md
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
…ettings-shift-esc

* upstream/main: (91 commits)
  test(browser): click timeline rows only after the timeline settles (block#2560)
  ci: split Vitest across six shards so 3/4 stops hitting the job cap (block#2571)
  feat(github): unfurl PR and issue links inline with their title (block#1693)
  fix(auth): style federated sign-in callback pages (block#2549)
  fix(agents): align creation model controls with identity (block#2480)
  Run native Codex through plugin execution targets (block#2150)
  test: split Solo access browser scenarios (block#2499)
  feat(agents): let any agent change Buzz settings through the MCP (block#2248)
  Let plugins decorate host sidebar rows and add sort modes (block#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083)
  feat(agents): move version history into settings (block#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (block#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465)
  feat(plugins): let agents request plugin changes for owner review (block#2280)
  fix(messages): keep the hover toolbar while its menu is open (block#2439)
  feat(workflows): suggest channel members for @ in Send Message text (block#2309)
  AIDA-2344: Exclude muted channels from Dock (block#2313)
  feat(search): add slack-style message search operators on desktop (block#2463)
  test: fix invitation and coordination checks on main (block#2517)
  ...

Signed-off-by: Cheng Peng <cheng@block.xyz>
Co-authored-by: Cursor <cursoragent@cursor.com>
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
* upstream/main: (22 commits)
  test(browser): click timeline rows only after the timeline settles (block#2560)
  ci: split Vitest across six shards so 3/4 stops hitting the job cap (block#2571)
  feat(github): unfurl PR and issue links inline with their title (block#1693)
  fix(auth): style federated sign-in callback pages (block#2549)
  fix(agents): align creation model controls with identity (block#2480)
  Run native Codex through plugin execution targets (block#2150)
  test: split Solo access browser scenarios (block#2499)
  feat(agents): let any agent change Buzz settings through the MCP (block#2248)
  Let plugins decorate host sidebar rows and add sort modes (block#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083)
  feat(agents): move version history into settings (block#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (block#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465)
  feat(plugins): let agents request plugin changes for owner review (block#2280)
  fix(messages): keep the hover toolbar while its menu is open (block#2439)
  feat(workflows): suggest channel members for @ in Send Message text (block#2309)
  AIDA-2344: Exclude muted channels from Dock (block#2313)
  feat(search): add slack-style message search operators on desktop (block#2463)
  test: fix invitation and coordination checks on main (block#2517)
  ...

Signed-off-by: Cheng Peng <cheng@block.xyz>
Co-authored-by: Cursor <cursoragent@cursor.com>
cheng-block added a commit to cheng-block/buzz-app that referenced this pull request Nov 5, 2026
* upstream/main: (91 commits)
  test(browser): click timeline rows only after the timeline settles (block#2560)
  ci: split Vitest across six shards so 3/4 stops hitting the job cap (block#2571)
  feat(github): unfurl PR and issue links inline with their title (block#1693)
  fix(auth): style federated sign-in callback pages (block#2549)
  fix(agents): align creation model controls with identity (block#2480)
  Run native Codex through plugin execution targets (block#2150)
  test: split Solo access browser scenarios (block#2499)
  feat(agents): let any agent change Buzz settings through the MCP (block#2248)
  Let plugins decorate host sidebar rows and add sort modes (block#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083)
  feat(agents): move version history into settings (block#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (block#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465)
  feat(plugins): let agents request plugin changes for owner review (block#2280)
  fix(messages): keep the hover toolbar while its menu is open (block#2439)
  feat(workflows): suggest channel members for @ in Send Message text (block#2309)
  AIDA-2344: Exclude muted channels from Dock (block#2313)
  feat(search): add slack-style message search operators on desktop (block#2463)
  test: fix invitation and coordination checks on main (block#2517)
  ...

Signed-off-by: Cheng Peng <cheng@block.xyz>
Co-authored-by: Cursor <cursoragent@cursor.com>
matt2e added a commit that referenced this pull request Nov 5, 2026
…e Retry

## Problem

WebKit's `sidebar-section-creation.spec.mjs:9` "Channels offers
empty-section creation with keyboard focus and confirmed persistence"
failed at line 167 when clicking "Retry preferences". The click timed out
after 10 s. Its call log says "element is not enabled", then "element was
detached from the DOM".

- clay/bestie-empty-state: run 41547288213,
  https://github.com/block/buzz-app/actions/runs/41547288213/job/125626193138
- kylez_NEXUS-4082_channel_unread_boundary: run 41553877476 attempt 1,
  https://github.com/block/buzz-app/actions/runs/41553877476/job/125648251816
  (attempt 2 passed)

## Cause

The journey's second creation fails the strong channel-sections writer
read with an injected 502. `isTransientFailure` counts a 502 as
transient, so the preference store publishes the error with `transient`,
and relay recovery reads again after `RECOVERY_DELAYS_MS[0]` (1000 ms).
That read disables Retry preferences (`disabled={refreshing}`), and its
success removes the button. This is intended: recovery owns transient
background reads.

The trace from run 41547288213 shows the race. The 502 was fulfilled at
153289 ms and the click started at 153482. WebKit's first "visible,
enabled and stable" check did not return until 154493. Recovery's read
reached the route at 154393, so that check found the button disabled.
The button detached at 155188, and the click timed out. Any click that
takes more than about a second to land loses the Retry to recovery.

## Fix

Inject a 403 instead. A denial is not transient, so `loadState` reports
it as settled and recovery leaves it alone. The manual Retry is then the
only way out, which is the path this journey exists to exercise. The
fixture already uses this reasoning for activity reads ("Activity reads
retry a 502 quietly, so their journeys inject a 403").

The failure is recorded under `app.report.relayFailures`, the fixture's
allowance for injected relay 403s, rather than
`sidebarPreferenceFailures`, which only matches 502s. The check that the
alert doesn't show the status now looks for "403". A comment explains
why a transient status would race recovery. The existing `writerReads`
of 3 after the click now also confirms that no background read ran
first.

Only the test changes (+7/-4). No other assertions change, and no
product code changes or browser cases are added or removed.

Open PR #2150 ("Run native Pi agents through plugin execution targets")
makes the same 403 and `relayFailures` change in this hunk as part of a
feature change. It leaves the alert check on "502". When #2150 rebases,
it can take this side of the hunk.

## Validation

Local Linux workstation, Playwright 1.63.0, `--no-deps --workers=1
--retries=0`:

- Adversarial ordering, using temporary, uncommitted changes: the Retry
  click was delayed 1.2 s, modelling the slow actionability check, and
  the first writer read after the injected failure was held for 600 ms.
  Without the fix: 10/10 failed (5 WebKit, 5 Chromium), all at the Retry
  click with "element is not enabled" followed by "element was detached
  from the DOM", the CI signature. Holding the read without delaying the
  click: 6/6 passed. With the fix: 10/10 passed, and 6/6 passed with a
  3.5 s delay, past recovery's first two retries.
- Natural contention: WebKit pinned to 4 CPUs shared with 4 busy loops,
  2 workers, 24 repeats. Without the fix: 24/24 passed, so load alone did
  not open the window locally. With the fix: 24/24 passed.
- `bin/pnpm test:browser tests/browser/sidebar-section-creation.spec.mjs
  --project webkit --project chromium --no-deps --workers=1
  --grep "empty-section creation" --repeat-each=20`: 40/40 passed.
- Full `sidebar-section-creation.spec.mjs`, both engines: 6/6 passed.
- `biome check` and `git diff --check` are clean.

A green run doesn't prove the flake is gone. Hosted CI covers broad
validation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
(cherry picked from commit 69eae17b94b702a9c74c154e37ea147441c7ab11)
ljknight added a commit to ljknight/buzz-app that referenced this pull request Nov 5, 2026
…nks-in-browser

* origin/main: (138 commits)
  feat(composer): optional ⌘/Ctrl+Enter to send (block#2331)
  test: use keyboard activation for reply caret setup (block#2557)
  test: settle member search before invitation expansion (block#2532)
  test(agent-command): isolate the copied executable writer (block#2537)
  test(sidebar): wait for the rolled-back row to leave Starred before collapsing Work (block#2574)
  test(browser): click timeline rows only after the timeline settles (block#2560)
  ci: split Vitest across six shards so 3/4 stops hitting the job cap (block#2571)
  feat(github): unfurl PR and issue links inline with their title (block#1693)
  fix(auth): style federated sign-in callback pages (block#2549)
  fix(agents): align creation model controls with identity (block#2480)
  Run native Codex through plugin execution targets (block#2150)
  test: split Solo access browser scenarios (block#2499)
  feat(agents): let any agent change Buzz settings through the MCP (block#2248)
  Let plugins decorate host sidebar rows and add sort modes (block#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083)
  feat(agents): move version history into settings (block#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (block#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465)
  feat(plugins): let agents request plugin changes for owner review (block#2280)
  ...

# Conflicts:
#	src/bundled/github/README.md
matt2e added a commit that referenced this pull request Nov 5, 2026
…2566)

## Summary

Six browser journeys each acted before a boundary they depended on: a
debounced search landing, a background recovery read (in two journeys),
a message reveal taking focus, a cold page load that fetched the fixture
module by module, and a pause-then-seek in the same tick. Each commit
adds the missing barrier, or removes the race at its source. All six
changes are test-only: no assertions are loosened, no retries, sleeps or
timeouts change, no browser cases are added or removed, and no product
code changes.

### test(channels): let the member search land before the final pointer
expansion

**Problem**

WebKit's `channel-members-focus.spec.mjs:826` "final invitation
expansion continues keyboard focus at the first new row" failed 5 times
between 2026-11-02 23:00Z and 2026-11-04 11:00Z, on 4 unrelated branches
including main. Every failure was in the pointer iteration at :853:
"Your other agents" held 8 "Add Helper" rows, never 10.

- main, run 41662691884:
https://github.com/block/buzz-app/actions/runs/41662691884/job/126010368608
- sol/aida-2436-recipient-search:
https://github.com/block/buzz-app/actions/runs/41717923671/job/126203332188
- kylez_NEXUS-4155_solo-model-effort-per-chat:
https://github.com/block/buzz-app/actions/runs/41748955704/job/126309380524
and
https://github.com/block/buzz-app/actions/runs/41752221861/job/126320136617
(attempt 2 passed)
- kylez_NEXUS-4165_agent-create-thinking:
https://github.com/block/buzz-app/actions/runs/41799304245/job/126471226006

**Cause**

Typing "Helper" shows the local agent rows at once. The relay search
runs after `SEARCH_DEBOUNCE_MS` (300 ms) and adds a "People not in this
channel" group above "Your other agents". In all five traces, the
pointer click started 185–260 ms after the fill, while the list was
still `aria-busy`. The click passed Playwright's hit-target check inside
Show more's box (y 523–547). The People group then landed and moved Show
more to y 543–567, so the press hit Show more and the release hit the
row below it, and no click reached the button.

**Fix**

After filling the search, wait for the Member list's
`data-refreshing="false"`, as this file's large-search test already
does. The search keeps `loading` true from the moment the text changes
until that text's results arrive, so the barrier can't pass early.

**Validation**

- Adversarial ordering, with a fixture gate that held the relay search
until `pointerdown` on Show more and the debounce lowered to 100 ms:
without the fix 20/20 failed at :853 with "Received: 8", and with the
fix 20/20 passed.
- Natural contention, with 4 CPUs shared with 4 busy loops: without the
fix 1/24 failed with the CI signature, and with the fix 48/48 passed.
- Both engines, `--repeat-each=30`: 60/60 passed. The full file, both
engines: 36/36 passed.

### test(channels): deny the failed preference read so recovery can't
race Retry

**Problem**

WebKit's `sidebar-section-creation.spec.mjs:9` "Channels offers
empty-section creation with keyboard focus and confirmed persistence"
failed at :167 clicking "Retry preferences". The call log said "element
is not enabled", then "element was detached from the DOM".

- clay/bestie-empty-state, run 41547288213:
https://github.com/block/buzz-app/actions/runs/41547288213/job/125626193138
- kylez_NEXUS-4082_channel_unread_boundary, run 41553877476 attempt 1:
https://github.com/block/buzz-app/actions/runs/41553877476/job/125648251816
(attempt 2 passed)

**Cause**

The journey failed the channel-sections writer read with a 502. A 502
counts as transient, so relay recovery reads again after 1 s. That read
disables Retry preferences, and its success removes the button. In the
trace, WebKit's first actionability check for the Retry click returned
100 ms after recovery's read reached the route. Any click that takes
about a second to land loses the Retry to recovery.

**Fix**

Inject a 403 instead. A denial isn't transient, so recovery leaves it
alone, and the manual Retry is the only way out, which is the path this
journey exists to exercise. The failure is recorded under
`app.report.relayFailures`. The check that the alert doesn't show the
status now looks for "403".

**Note on main's #2150**

#2150 merged to main after this commit was written (as `21f6b97a99`). It
makes the same 403 and `relayFailures` change in this hunk, with its own
one-line comment, so the cherry-pick conflicted there. The conflict was
resolved by keeping this commit's side. On this branch, the commit
therefore only replaces that comment with the longer explanation and
changes the alert check from "502" to "403". On main that check passes
no matter what, because the injected status is already 403. The commit
body still refers to #2150 as an open PR. No other adjustment was
needed.

**Validation**

- Adversarial ordering, with the Retry click delayed 1.2 s and the first
read after the failure held for 600 ms:
  - without the fix: 10/10 failed with the CI signature
  - with the fix: 10/10 passed, and 6/6 passed with a 3.5 s delay
- Both engines, `--repeat-each=20`: 40/40 passed. The full file, both
engines: 6/6 passed.

### test(inbox): wait for the entry reveal before filling an opened
conversation

**Problem**

WebKit's `inbox-archive.spec.mjs:658` "reply and fresh mention reopen
their filters without replacing composer draft" failed on attempt 1, and
the same SHA passed on rerun. The composer was empty: `Expected: "Unsent
mention retention draft"`, `Received: ""`.

- peon/people-relationships-cache, run 41752428902:
https://github.com/block/buzz-app/actions/runs/41752428902/job/126320899490

**Cause**

Opening a conversation reveals its entry reply. `useMessageReveal`
focuses the row two frames after the thread target is ready. The journey
filled the composer as soon as it appeared, so on a slow runner the
reveal landed during the fill. Playwright focuses the editor and selects
its contents, and then inserts text at the selection. In WebKit, the row
taking focus clears that selection, so nothing is inserted. A probe that
focused the row between the two steps reproduced it in WebKit but not in
Chromium. The trace shows the only DOM change during the fill was the
entry row gaining `tabindex=-1`. Three more journeys in the file share
the race. The existing in-view wait in one of them isn't a barrier,
because the row is in view before the reveal runs.

**Fix**

Add a `revealed()` helper. It waits for the conversation list's
`aria-busy` to be false, for the entry row to be focused, and then two
frames. All four journeys that fill after Open now call it. The
product's focus policy is addressed separately in #2563.

**Validation**

- Adversarial ordering, with the reveal held 800 ms and run as soon as a
fill placed its selection: without the fix 17/24 failed, and with the
fix 24/24 passed.
- The four journeys, both engines, `--repeat-each=10`: 80/80 passed. The
full file, both engines, `--repeat-each=2`: 40/40 passed.

### test(voice-notes): serve the fixture as a build so WebKit's first
load fits

**Problem**

The "WebKit recording (macOS)" lane failed in the first case of
`voice-notes.spec.mjs` (:138), in the shared beforeEach.
`page.goto(.../tests/fixtures/voice-notes.html)` exceeded the 15 s
navigation timeout.

- renovate/all-minor-patch, run 41755043277:
https://github.com/block/buzz-app/actions/runs/41755043277/job/126329063633
- peon/dnd-stable-transform, run 41757728323 attempt 1:
https://github.com/block/buzz-app/actions/runs/41757728323/job/126337712196
(attempt 2 passed)

**Cause**

The fixture runs the real PluginRuntime, ConversationService, relay
session and Voice Notes plugin, and the Vite dev server served that
graph module by module. The traces show about 816–818 requests. Vite's
median wait was about 50 ms, but the median receive was about 1.0–1.14
s, and the first request to the last response took 13.3–14.5 s. On a
cold 3-core macOS runner, WebKit's per-module round trips alone nearly
fill the budget. #1971 only warmed the server's transforms.

**Fix**

- Build the fixture once in `beforeAll` and serve it with `build` and
`preview` from `vite-server.mjs`, as four other specs already do. The
page now loads in 4 requests instead of 838.
- The build keeps development React (`import.meta.env.DEV` and
`process.env.NODE_ENV` are defined as `build.mjs` does), so StrictMode's
double effects still apply.
- The last case used to import
`/src/features/messages/attachment-draft.ts` from the dev server. The
fixture now exposes `attachmentDraft` bound to its session instead.

**Validation**

Cold `page.goto` to load, WebKit (WPE), one new browser per run:

| CPUs, busy loops | dev server | build |
| --- | --- | --- |
| 3, none | 1.15–1.23 s | 0.53–0.65 s |
| 3, 3 | 2.0–2.85 s | 0.44–1.21 s |
| 2, 8 | 3.7–6.8 s | 1.1–2.55 s |
| 1, 3 | 5.1–6.1 s | 2.0–2.3 s |

- CI config, Chromium (all five cases) and Linux WebKit (the untagged
case), `--repeat-each=10`: 60/60 passed. Pinned to 3 CPUs with 3 busy
loops, `--repeat-each=5`: 30/30 passed.
- **The macOS lane couldn't be validated here.** Its selection
(`--project webkit --grep @media-recorder`) can't pass on Linux, because
Playwright's Linux WebKit has no MediaRecorder. Before and after this
change, those cases fail at the same step locally. Hosted macOS CI is
the first real check of the fix.
- `tsc --noEmit` is clean, and the flake-check listing test in
`tests/integration/flake-check.test.mjs` that reads this spec passes.

### test(github): seek the fullscreen PR video without pausing it in the
same tick

**Problem**

WebKit's `github-body.spec.mjs:35` "PR media plays under the packaged
media policy in a narrow GitHub panel" failed at :189. The fullscreen
video's `seeked` fired, but `currentTime` was about 0.5 instead of 15.

- peon/federated-session-bundle, run 41567557720 attempt 1 (Received
0.533400327):
https://github.com/block/buzz-app/actions/runs/41567557720/job/125692945945
(the rerun passed)
- renovate/all-minor-patch, run 41683322444 (Received 0.48949321):
https://github.com/block/buzz-app/actions/runs/41683322444/job/126083327417

**Cause**

The fullscreen `VideoPlayer` autoplays and, on `loadedmetadata`, seeks
to the inline video's position. The journey then paused the playing
video and set `currentTime` in the same evaluate. There were two
candidate mechanisms:

- (a) the restore lands after the journey's seek and overwrites it
- (b) WebKit completes a seek issued in the same tick as `pause()` on a
playing video without updating `currentTime`

Local probes support (b). About 0.2% of reloaded samples were unmoved:
`seeked` fired, but `currentTime` didn't change. In those samples the
restore always came before the journey's seek, and
`requestVideoFrameCallback` showed a frame at 15.2 s while `currentTime`
still read under 0.2. Across 1,800 reopen samples, `loadedmetadata`
never landed after the journey's seek.

**Fix**

Before the journey opens the viewer, a MutationObserver pauses the
fullscreen video as soon as it mounts, so it never plays. The observer
also resolves on the video's first `seeked`, which is the restore's. The
journey waits for that before its own seek, and no longer calls
`pause()` in the seek evaluate. This covers both candidate mechanisms:
the video has never played (b), and the restore has already landed (a).
The assertion stays exact (`toBeCloseTo(…, 1)`), and the two inline
videos still cover playback.

**Validation**

- Reload A/B, WebKit, 30 repeats × 50 samples per mode, 4 CPUs shared
with 2 busy loops:
  - old: 3/1,500 unmoved, all with the CI signature
  - new: 0/1,500 unmoved

That fits (b), but it isn't statistically conclusive (one-sided p ≈
0.12).
- Reopen A/B: 0/900 unmoved in both modes, and the restore never landed
late (0/1,800).
- "PR media plays", both engines, `--repeat-each=10`: 20/20 passed.
WebKit pinned to 4 CPUs with 4 busy loops, `--repeat-each=16`: 16/16
passed.

### test(channels): deny the retried preference read so recovery can't
race the final Retry

**Problem**

Found in code review, following up the sidebar-section-creation commit
above. `navigation-groups.spec.mjs` "Create new retains its draft when
preferences fail before submission and recovers in place" injects 502s
on `sidebar-preferences` and clicks "Retry preferences" twice, the
second time right after `page.unroute`. Recovery's next background read
can succeed and remove the alert before that click lands. No CI failure
of this journey was looked up. The race was found by reading the code
and confirmed with a forced delay.

**Cause**

- The first 502 starts recovery, which retries after 1 s and again about
2 s later, and the route fails both.
- The dialog's Retry calls `preferences.reload`. That cancels recovery's
timer but keeps its attempt count, so the next background read is
scheduled 2 s or 4 s after the manual Retry fails.
- After the unroute, that background read succeeds, and the alert
unmounts with its button before a slow final click lands.

**Fix**

Keep the first failure a 502, so recovery still owns it and shows the
"Reconnecting to relay…" notice the journey asserts. From the first
manual Retry on, the route answers 403, so recovery stays idle and the
final click is the only thing that can recover preferences. The 403s go
under `app.report.relayFailures`, and the 502 stays under
`sidebarPreferenceFailures`.

**Validation**

- A forced wait between `page.unroute` and the final click, both
engines:
  - without the fix, 4.5 s: 4/4 failed with the click timing out
  - without the fix, 1.5 s: 6/6 passed, consistent with the 2–4 s window
  - with the fix, 4.5 s: 6/6 passed
- Both engines, `--repeat-each=10`: 20/20 passed. The full file, both
engines: 20/20 passed.
- Rebased on main `f4805605f5`: `tsc --noEmit`, `biome check
--error-on-warnings` on the seven changed files and `git diff --check`
are clean. Biome's one info-level `useLiteralKeys` note is on an
untouched line that is the same on main. `node --test
tests/integration/flake-check.test.mjs` passes 10/10. In WebKit and
Chromium, `--workers=4 --retries=0`, `channel-members-focus`,
`sidebar-section-creation`, `inbox-archive`, `github-body` and
`navigation-groups` in full passed 84/84. `voice-notes.spec.mjs` under
`playwright.ci.config.mjs`, Chromium and Linux WebKit: 6/6 passed. The
macOS recording lane was not run, as noted above.

A green run doesn't prove the flakes are gone. Hosted CI covers broad
validation.

## Coordination

- Draft #2532 adds the same `data-refreshing` wait in
`channel-members-focus.spec.mjs` and conflicts textually with this
branch. It duplicates the first commit, so only one of the two should
land.
- #2543 (open, head `c1dcf443`) deletes `inbox-archive.spec.mjs` and
moves its draft journeys into a new `inbox-mark-read.spec.mjs`. There it
works around the same reveal-vs-fill race with a `typeDraft` helper that
refills until the text lands (`expect(...).toPass()`). Git reports a
modify/delete conflict with this branch. A resolver who just takes the
delete drops the `revealed()` fix. Porting the helper into
`inbox-mark-read.spec.mjs` would replace the retry loop with a real
barrier.
- #2150 is merged. Its overlap in `sidebar-section-creation.spec.mjs`
was resolved as described in the second commit's section.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: mtoohey <mtoohey@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
damienrj added a commit to damienrj/buzz-app that referenced this pull request Nov 5, 2026
* commit 'a39dba132^':
  feat(github): unfurl PR and issue links inline with their title (block#1693)
  fix(auth): style federated sign-in callback pages (block#2549)
  fix(agents): align creation model controls with identity (block#2480)
  Run native Codex through plugin execution targets (block#2150)
  test: split Solo access browser scenarios (block#2499)
  feat(agents): let any agent change Buzz settings through the MCP (block#2248)
  Let plugins decorate host sidebar rows and add sort modes (block#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083)
  feat(agents): move version history into settings (block#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (block#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465)
  feat(plugins): let agents request plugin changes for owner review (block#2280)
  fix(messages): keep the hover toolbar while its menu is open (block#2439)

Signed-off-by: damienrj <damien@squareup.com>
ljknight added a commit to ljknight/buzz-app that referenced this pull request Nov 5, 2026
* origin/main: (121 commits)
  feat(composer): optional ⌘/Ctrl+Enter to send (block#2331)
  test: use keyboard activation for reply caret setup (block#2557)
  test: settle member search before invitation expansion (block#2532)
  test(agent-command): isolate the copied executable writer (block#2537)
  test(sidebar): wait for the rolled-back row to leave Starred before collapsing Work (block#2574)
  test(browser): click timeline rows only after the timeline settles (block#2560)
  ci: split Vitest across six shards so 3/4 stops hitting the job cap (block#2571)
  feat(github): unfurl PR and issue links inline with their title (block#1693)
  fix(auth): style federated sign-in callback pages (block#2549)
  fix(agents): align creation model controls with identity (block#2480)
  Run native Codex through plugin execution targets (block#2150)
  test: split Solo access browser scenarios (block#2499)
  feat(agents): let any agent change Buzz settings through the MCP (block#2248)
  Let plugins decorate host sidebar rows and add sort modes (block#1853)
  NEXUS-4082: Keep the new-message boundary stable during a visit (block#2083)
  feat(agents): move version history into settings (block#2374)
  fix(agents): read goose Activity by saved session ids and render memory bodies on open (block#2466)
  feat(channels): starter channels: admin switch and relay auto_join flag (block#1624)
  fix(plugins): read Square HTTPS sources with the Block SSH agent (block#2465)
  feat(plugins): let agents request plugin changes for owner review (block#2280)
  ...

# Conflicts:
#	docs/buzz-help.md
dr0uleau added a commit that referenced this pull request Nov 5, 2026
## Why

Native Claude agents need to use a remote execution provider while they
keep Buzz tools, hooks, and saved conversations.

## What

Run Claude through the shared execution interface. Keep each remote
conversation on its saved target across app restarts. Stop cancels
setup, active turns, and compaction.

## How

The provider owns the target and workspace. Claude uses the target’s
installed CLI and authentication. Tools and hooks use its stream-json
connection. File reads use the provider. Device credentials stay on the
device.

Remote Settings use the target defaults or an explicit model. Remote
usage totals and transcripts are unavailable. Local Claude keeps its
device defaults.

## Risk

This changes Claude session lifecycle code that local agents also use.
Remote restore failures keep the saved target and require an explicit
new session.

## Testing

The signed macOS arm64 debug app at `d66fcdc4` passes native Claude
acceptance with the checked Nexus plugin. The current head adds only a
help correction to this source.

- Restore saved sign-in, create a Claude agent, and bind Blox: pass.
- Ask Claude to create and read a marker file on Blox: pass. The native
chat shows the exact text. An independent remote read confirms the file.
- Stop an active terminal tool: pass. The remote process exits and the
session returns to idle.
- Restart the same app, restore the saved task, and read the existing
file: pass. The Claude session ID and Blox workspace stay the same.
- Verify Buzz tools and `UserPromptSubmit`, `PreToolUse`, and
`PostToolUse` hooks in the remote stream: pass.

The operator confirms the native app test passes. No browser journeys
are added or removed. This test uses the original approved binary. It
does not prove credential access across different builds.

## Bigger picture

This uses the execution interface from [PR
#2150](#2150). Execution providers
keep their own target policy and commands.

buzz-review-completed

Generated with Codex

---------

Signed-off-by: Daniel Rouleau <drouleau@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants