Skip to content

fix(server): a thread's strip no longer takes in another agent's tab - #207

Merged
immakermatty merged 1 commit into
mainfrom
lazurio/thread-tabs-foreign-binding
Nov 14, 2026
Merged

immakermatty merged 1 commit into
mainfrom
lazurio/thread-tabs-foreign-binding

Conversation

@immakermatty

@immakermatty immakermatty commented Nov 14, 2026 •

Copy link
Copy Markdown

Status: in progress (Draft). Companion to Lazurio/LazurioPlatform#986, which fixes Lazurio/LazurioPlatform#974 on the Platform side.

Problem

The overlay adds to a thread's strip any tab the thread's agent-browser session t3-<id> newly binds (LazurioPlatform F47 point 4). agent-browser (0.37, and still the 0.43 candidate) writes a binding even after a failed tab <id>, and that binding names the tab a fresh daemon picked itself at attach: the first live tab of the browser, whoever's it is. In pingdotgg#974, lazurio browser window failed to bind a thread's session to its new window, and the session was left on a tab another agent had pinned and was working in. The watch on the socket directory saw the binding within 100 ms, so the strip held that tab and the thread's panel showed it.

LazurioPlatform#986 now verifies the binding and removes one a failed bind left on another tab. That happens only after the daemon has written it (about 0.2 s measured, longer when the command outlasts the runner), which is still well inside this watch's 100 ms debounce. So the overlay has to refuse the tab too.

Fix

A new binding of the thread's session is taken into the strip only when one of these holds:

  • the thread's strip has the tab already (a reveal, as before), or
  • no other strip holds the tab and no other session's binding names it.

readBindings keeps which sessions each tab is bound to (boundBy), and refresh checks it together with the holder files it already reads. A skipped binding logs thread-tabs-binding-not-recorded. preview_open, a person's holds, archive and the reconciliation are unchanged.

Why not "only bindings confirmed by a window record"

LazurioPlatform pingdotgg#971 writes windows/<session>.json only for a new window that lazurio browser window or lazurio apps open opened. Taking in only bindings that such a record confirms would drop the tabs F38 point 2 relies on: lazurio browser open binds the thread's session to a tab the hub opened, without a record, and T3 shows that tab only because the binding is new. It would also drop an agent's own tab new inside its session. The record answers whose window a session is. It does not say which tab an unrecorded binding names, so the overlay refuses what is evidently another's instead.

Verification

  • apps/server/src/lazurio/threadTabs.test.ts, new case in "tabs an agent opens": a binding to a tab another session is bound to, or another strip holds, is not taken in; one the strip holds already is revealed; the thread's own next window is recorded. It fails on main (expected [ { …(2) } ] to be null) and passes here (37/37 in the file).
  • The seam assertion in scripts/lazurio-release-contract.test.mjs follows the new shape (52/52).
  • vp run check:changed: format, lint, knip, graphql and typecheck pass. src/provider/AgentContextBudget.test.ts fails locally (measured codexAdditionalContextBytes 8838 against 7327). It fails the same way with a clean environment, it does not touch this change, and Fork CI on main 439c780 passed it. The local run used Node 24.22.2, not the pinned 26.12.1. CI decides.
  • docs/operations/lazurio-overlay.md ("Jak záložka do pásu vlákna přijde") states the rule.

Only files in the overlay's own apps/server/src/lazurio/, its spec and its contract test change, so allowed_upstream_changes is untouched.

Made with Claude Opus 5.5 in Claude Code (T3 Code thread).

Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 22786896-3028-4190-962d-8b4986732ca0

🤖 Generated with Claude Code

Note

Fix thread strip conflicts so a thread only takes tabs it owns

  • A thread's tab strip no longer pulls in tabs held by another strip or named by another session's binding. Bindings that conflict are skipped and logged; the tab is still taken if the thread's own strip already contains it.
  • threadTabs.ts now tracks which session's binding names each tab ID via the readBindings reader and the new anothersTab conflict helper. The refresh processor uses this to guard each fresh thread-session binding.
  • Updates the contract test, regression tests in threadTabs.test.ts, and the overlay docs.
  • Behavioral Change: resolved bindings that previously recorded unconditionally are now skipped when the conflict check fails; bindings for threads that cannot be resolved are also skipped.

Macroscope summarized 4b910af.

The overlay records any tab the thread's agent-browser session newly binds
(LazurioPlatform F47 point 4). agent-browser records a fresh daemon's own pick
of a tab, the first live one of the browser, even after a failed `tab <id>`,
so `lazurio browser window` could leave the thread's session on a tab another
agent had pinned and was working in (Lazurio/LazurioPlatform#974). The watch
on the socket directory picks such a binding up within 100 ms and the strip
then holds and shows that tab. The Platform now removes such a binding after
a failed bind, but only after the daemon has written it.

A new binding is now taken into the strip only when the thread's strip has
the tab already, or no other strip holds it and no other session's binding
names it. preview_open and a person's holds are unchanged.

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

@agentrozjedemeai agentrozjedemeai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No blocking findings.

Non-blocking: The focused thread-tabs tests and release contract passed. check:changed passed format, lint, knip, GraphQL and typecheck, but its unrelated AgentContextBudget test failed locally on Node 24.22.2 (the repo pins 26.12.1); CI is still running. I did not verify the behavior in a browser.

Order and migrations: release this overlay together with or before Lazurio/LazurioPlatform#986 to close the interval before its failed-binding cleanup.

— Pablo

@immakermatty
immakermatty merged commit 40f8eeb into main Nov 14, 2026
7 checks passed
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.

lazurio browser window: místo nového okna zapíše vláknu cizí záložku jiného agenta

2 participants