Skip to content

fix(ui-server): publish tracker completion after Activity commits - #2983

Merged
schlessera merged 1 commit into
mainfrom
fix/2900-unwatched-tracker-done-044
Oct 26, 2026
Merged

schlessera merged 1 commit into
mainfrom
fix/2900-unwatched-tracker-done-044

Conversation

@schlessera

@schlessera schlessera commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Closes #2900

What changed

The hosted session runner now waits for terminal Activity writes before sending its final recovery notification. Work that finishes while its conversation is unwatched reaches the tracker with the host's completion time and survives reload and seen observation. The affected product surface is ui-server session recovery for all backends. The tracker fixture, its 15-second waits, every existing assertion and all ui-react product code are unchanged.

Why this shape

A first-parent git bisect run, using only the original 320px dark/coarse-pointer cell in offline real Chrome, identified b192ca0 (#2830) as the first bad main commit. Its parent 1f032f9 passes. There were no skipped revisions. The write-worker conversion made recorder.finish() asynchronous but placed the commit wait after the final idle frame.

The captured terminal/catalog/WebSocket/store/view timeline shows result reaching the browser: the tracker becomes done with endedAt === null. The catalog names the correct request, revision and turn. The result-triggered recovery read returns running, and the final idle-triggered read returns unknown before the terminal rollup commits. The database later contains the successful terminal rollup and end time, but no subsequent notification requests another read.

  • Recovery requires the committed rollup (const rollup, packages/ui-server/src/ws/recovery.ts:133-144).
  • The tracker client refreshes on the end notification (const ended, packages/ui-react/src/lib/tracker-client.ts:323-330); an in-flight read queues a follow-up read.
  • The fix preserves authority release before the existing recorder wait, then sends the notification before a queued successor starts (releaseActiveAuthorization();, packages/ui-server/src/ws/run-session.ts:945-956).

This restores D52 Recovery A without new polling, inferred timestamps, wire shapes or schema changes. The SQLite write-worker decision records the ordering requirement. A patch changeset names @schlessera/brain-ui-server; no compatibility contract changes.

Proof

Base c670449. Head 16dc0c7.

  • Failing-first: the original dark cell fails at tracker-runtime.test.ts:167 on base and every bisected bad revision; the verified good parent passes. Both new held-commit success/error server cases fail before the fix on ACTIVITY_RECOVERY_IDLE_AFTER_COMMITTED_TERMINAL: an early scoped idle exists while the write is held.
  • Focused green: complete Activity/recovery server files 43 pass / 0 fail, 268 assertions. Original tracker-runtime file in offline real Chrome 12 pass / 0 fail, 128 assertions, including all six reported cells.
  • Server mutation: reverting only packages/ui-server/src/ws/run-session.ts makes both new held-commit cases fail on the same named assertion.
  • Browser mutation: the same product-only revert makes all six original browser cells fail at line 167, each on the unchanged 15000 ms timeout: 0 pass / 6 fail, 18 assertions, 101.56 seconds. The fix was restored and the tree verified clean.
  • Full ui-react: head 2521 pass / 0 fail, 25331 assertions, 162 files, 1175.44 seconds. Base 2515 pass / 6 fail, 25054 assertions, the same 162 files; every base failure is one original line-167 tracker cell. All 12 tracker-runtime cases pass within the completed head suite.
  • Full ui-server: head 4053 pass / 0 fail, 27897 assertions, 339 files, 2346.97 seconds, exit 0.
  • Full root ./tests: head 1966 pass / 0 fail, 74408 assertions, 179 files, 667.16 seconds. Base 1966 pass / 0 fail, the same assertions and files, 657.81 seconds. Both exit 0. The head control uses the same checkout and unchanged commit as the earlier failed head run.
  • Retained root failures: an earlier same-head full run was 1963 pass / 3 fail, 74310 assertions. Its Claude governed native-Write refusal at policy-write-boundary.test.ts:285 (tests: policy-write-boundary claude governed native Write intermittently refused as not governed #2899), Pi interactive scratch-access assertion at line 80 (tests: Pi interactive policy boundary intermittently reports scratch unwritable #2987), and Pi governed shell-visibility assertion at line 287 (tests: Pi governed policy boundary intermittently misses the shell write #2988) remain recorded. Isolated complete policy files also pass 19/19 on head and base. There was no code/assertion/timeout change between the failed and passing head runs. The passing controls establish variability, not a cause or a repair of those failures.
  • Preflight: bun run check:pr --base origin/main passes on final head: 755 pass / 0 fail, 5033 assertions. Typecheck (bunx --no-install tsc --noEmit) passes. Pending changeset validation and bunx --no-install changeset status pass; the lockstep release plan remains minor.
  • Provider history: Claude/Pi guards 31 pass / 0 fail, 259 assertions.
  • Native hosted proof: Claude/Pi × UID 0/1000 full matrix passes, including an independently verified retained-index negative control and complete owned-resource cleanup. Matrix head 61d679149 has identical product/native inputs; the only later edit makes a new test's optional method call type-safe. A fresh Claude UID 1000 cell passes on final head 16dc0c753, including complete cleanup.

Completed package/root/preflight proof used the coordinator's shared heavy-command wrapper. The coordinator authorized the remaining #2900 broad-proof commands to run sequentially outside that wrapper as one temporary third slot; the additional unchanged-head root control also ran sequentially. Real Chrome, offline namespaces, capability drops and every guard remained enabled. Native proofs used the separate exclusive native lock. Completed logs and exit receipts are stored in persistent storage; reboot-interrupted attempts and queue cancellations are retained separately and are not counted as completed test proof.

The completed broad-command sequence was:

# Final head 16dc0c7533b8c3bb4242bfe7f20f6b484cbb3b9a:
BRAIN_REQUIRE_CHROME=1 scripts/test-offline.sh -- bun run test packages/ui-server

# Unchanged base c670449d3bf1b0639e50ce554d728961f4313add, in a separate checkout:
BRAIN_REQUIRE_CHROME=1 scripts/test-offline.sh -- bun run test ./tests

# One unchanged-final-head full-root control, retaining the original red receipt:
BRAIN_REQUIRE_CHROME=1 scripts/test-offline.sh -- bun run test ./tests

Checklist

  • Focused local preflight/behavioral receipts and complete selected hosted proof passed; actual head/base/job checkout evidence is in Proof.
  • Acceptance criteria from the issue are all met, or the gap is named below.
  • The PR and completed/advanced issues have verified release milestones under docs/process/github.md.
  • A changeset is included, or this changes nothing a consumer can see.
  • No personal data anywhere in the diff — the leakage gate covers the whole tree.
  • The integration contract index or relevant component is updated in this same commit, or nothing in the machine surface moved.
  • Docs under docs/ reflect the change, or nothing documented moved.
  • Provider history stays append-only (docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed.

Anything left open

All required local proof is complete. Earlier continuity (#2932), liveness (#2986), storage-refusal (#2989) and policy-boundary (#2899, #2987, #2988) failures remain retained observations. The complete final-head UI and root controls pass; passing controls do not claim to repair or close those follow-ups.

The coordinator owns ready-candidate hosted CI, readiness and merge. The first checklist item remains open for that hosted proof. Leave this PR draft.

@schlessera schlessera added this to the 0.44.0 milestone Oct 26, 2026
@schlessera schlessera added type: fix Something is broken and must behave the way it is documented. area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. area: ui-react React chat/files/voice components, stores, WebSocket transport. labels Oct 26, 2026
@schlessera
schlessera marked this pull request as ready for review October 26, 2026 05:36
@schlessera
schlessera merged commit bbb5a41 into main Oct 26, 2026
27 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui-react React chat/files/voice components, stores, WebSocket transport. area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. type: fix Something is broken and must behave the way it is documented.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui-react: mounted tracker runtime cells never see an unwatched turn finish

1 participant