- STAGING-OUTAGE regressions: degraded alarm fires (not silent) when the
set empties from a relaunch storm; self-heal re-inits a fresh set once the
kernel relaxes; a waiter queued during the dead window is served by
self-heal; a transient relaunch EAGAIN is retried and the entry survives
(no eviction, no alarm).
- Bounded serveNextWaiter transient re-drive + FIX#7 dead-vs-alive gate
propagation to the serve path.
- orchestrator: degraded/recovered signal wiring covered.
- BUG3 (orphan-by-recycle waiter drain) adapted to the crash-recovery
acquire path: an acquire whose in-flight open is orphaned re-enqueues as a
waiter; with cap=1 the freed slot goes to the other waiter, so the orphaned
acquire settles via its own (now bounded) timeout. The invariant it
verifies (freed capacity immediately serves the queued waiter) is unchanged.
Lift the soft nproc limit to the hard ceiling (`ulimit -u $(ulimit -Hu)`)
before exec'ing the orchestrator so the legitimate 40-context chromium
workload (several hundred OS threads at steady state) has ample thread
headroom instead of running near the default ~1024 soft ceiling, where
`chromium.launch()` tripped `pthread_create: Resource temporarily
unavailable`. `exec` keeps node as PID 1 for correct signal handling; the
`|| true` fallback keeps boot resilient when the runtime forbids raising
the soft limit (the cgroup pids limit then remains the dominant control).
Make the long-lived chromium pool survive a pthread/PID-ceiling
(`pthread_create: Resource temporarily unavailable`, errno 11) thread-
exhaustion storm instead of draining to an empty, permanently-wedged set.
- Crash-recovery relaunch backpressure: a transient EAGAIN on relaunch is
retried with bounded linear backoff before the entry is evicted, so a
thread-exhaustion window that relaxes within seconds recovers in place
rather than splicing the entry out of the set.
- Self-heal + degraded/recovered alarm: when the set empties mid-life the
pool fires an `onDegraded` red alarm (previously only emitted on init()
failure — mid-life death was silent) and kicks a background self-heal
loop that relaunches a fresh set the moment a launch succeeds, firing
`onRecovered`. No manual redeploy required.
- Bounded serveNextWaiter transient re-drive: a persistently-transient
newContext() on a still-connected browser no longer hot-loops the event
loop; it self-reschedules up to a ceiling then leaves the waiter queued
for a later release/recovery handoff (mirrors acquire()'s retry-once
semantics).
- Accounting hardening: generation-token guard on in-flight opens across a
recycle, clamped servedContexts rollback on orphan-close, deferred-recycle
re-check on non-release teardown paths, and waiter-drain on orphan-by-
recycle rollback so freed capacity is served immediately.
- orchestrator wires the pool's onDegraded/onRecovered hooks to the shared
`system:browser-pool-degraded` red/green capacity-loss signal.
Fixes the 2026-06-03 staging incident: the browser pool died from thread
exhaustion, the relaunch storm emptied the set, and the harness wedged with
no alarm -> 626 D0-red cells until a manual redeploy.
Removes the three internal-only skills (copilotkit-demo-parity, git-hooks,
showcase-demo-debugging) from .claude/skills/ and .agents/skills/. These are
staff-only and now live in the internal-skills plugin. Removing them at the
source means root skill discovery no longer sweeps internal skills into a
user's install.
## Problem
The top-level `docs/` app is retired, but nothing in the repo said so,
and contributors (and agents) kept editing it. Two parallel docs trees
plus a one-directional legacy sync script made it ambiguous where
documentation should be authored:
- `docs/content/docs/` — the old Fumadocs app, no longer publishing
- `showcase/shell-docs/src/content/docs/` — the live source for
docs.copilotkit.ai
Two instruction surfaces actively pointed the wrong way: `CLAUDE.md`
said nothing about docs at all, and `.claude/docs/hooks.md` told
contributors to "add a docs page under `/docs`" (the retired location).
## Change
Establish one canonical rule and reduce the other surfaces to pointers:
- **`.claude/docs/documentation.md`** (new) — source of truth.
CopilotKit docs are authored in `showcase/shell-docs/src/content/`
(`docs/`, `reference/`, `snippets/`, `framework-overviews/`); the
top-level `docs/` folder is retired; AG-UI protocol docs are authored
upstream in `ag-ui-protocol/ag-ui` (publishing to docs.ag-ui.com) and
mirrored into `content/ag-ui/`.
- **`CLAUDE.md`** — adds an Essentials hard-rule and a Reference link.
- **`docs/README.md`** — replaces boilerplate with a retired/STOP
banner; legacy README retained under a `<details>`.
- **`.claude/docs/hooks.md`** — fixes the stale `/docs` pointer and
clarifies that a hook's API reference page lives in
`reference/hooks/<hookName>.mdx`, where v2 reference navigation is
generated automatically from frontmatter (no `meta.json`); conceptual
guide pages under `docs/` still use `meta.json`.
- **`CONTRIBUTING.md`** — adds a two-domain documentation section for
human contributors.
## Notes
- Two docs domains: **CopilotKit docs** → shell-docs; **AG-UI protocol
docs** → upstream `ag-ui-protocol/ag-ui`, then synced into the in-repo
mirror.
- Instructions-only change; no enforcement hook or sync-process change.
- Markdown only; no package code touched.
Use `CopilotKit/CopilotKit/skills -y` instead of the repo root: root
discovery sweeps in the internal `showcase-demo-debugging` skill
(metadata.internal, lives in .claude/.agents, not skills/), so users got
12 skills incl. one internal. The /skills subpath yields exactly the 11
published skills. Drop -g so install defaults to project scope, letting each
project pin the skills version matching its CopilotKit dependencies.
The build-with-agents guide recommended a bare `npx skills add` that drops
human users into a multi-step interactive flow (skill multiselect, agent
selection, scope, install method, confirm). Recommend `-g -y` so all skills
install globally in one shot, with a Callout pointing to the flag-less command
for users who want to choose interactively.
The top-level docs/ app is retired but nothing said so, and two
instruction surfaces still pointed contributors there. Establish a
single canonical rule and reduce the other surfaces to pointers.
- Add .claude/docs/documentation.md as the source of truth: CopilotKit
docs are authored in showcase/shell-docs/src/content/; the top-level
docs/ folder is retired; AG-UI protocol docs are authored upstream in
ag-ui-protocol/ag-ui and mirrored here.
- CLAUDE.md: add an Essentials rule and a Reference link.
- docs/README.md: replace boilerplate with a retired/STOP banner.
- .claude/docs/hooks.md: fix the stale /docs pointer; document that a
hook's API reference page lives in reference/hooks/ and that v2
reference nav is generated from frontmatter (no meta.json).
- CONTRIBUTING.md: add a two-domain documentation section.
The .NET example's page.tsx pointed its Shared State and Generative UI doc
links at /pydantic-ai/ (copy-paste leftovers) instead of
/microsoft-agent-framework/. Also fix the threads-drawer CSS header comment
that still referenced 'mastra's CopilotSidebar'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The no-license locked panel is a fixed-width (w-80 / 18-20rem) block. In the
single-column mobile grid it left a dead background strip beside it and pushed
the app content down. Hide it below 1024px (max-lg:hidden / .lockedPanel
display:none) — consistent with the real drawer + first-paint placeholder,
which also reserve no column on mobile. Content now gets the full width.
Applied across all 7 migrated examples.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On the no-license locked threads panel:
- widen the panel slightly (w-72 -> w-80 / lockedPanel 18rem -> 20rem) and
add white-space: nowrap so `copilotkit add-intelligence` stays on one line
instead of wrapping mid-command
- add a 'with:' lead-in above the command box so it reads as a runnable command
rather than loose text
Applied across all 7 migrated examples (Card-based locked state in adk/agno/
langgraph-js/langgraph-python; themed .lockedPanel in mastra/ms-agent-*).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The disabled-state ThreadsPanelGate (shown when no Intelligence license is
present) rendered its locked card in a bare `w-72` container with no
background, so the threads column showed the page background (a black column in
dark-themed examples, white in light) instead of the drawer surface.
Give that container the drawer surface (bg + hairline right border) so the
locked card sits in a panel that matches the drawer, consistent with the
already-surfaced .lockedPanel examples (mastra, ms-agent-framework-*).
Affects adk, agno, langgraph-js, langgraph-python (the Card-based locked state).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ThreadsPanelGate renders a first-paint placeholder while the client-only
drawer mounts. It was either a bare `w-72` div (no surface → a black/white
column flash in the page bg) or a fixed 18rem inline-styled div (correct color
but, on mobile where the mounted drawer floats, the reserved 18rem collapsed on
mount → content shifted left).
Replace both with a shared `.drawerPlaceholder` class that matches the open
drawer's footprint + surface (18rem, drawer bg, hairline border) on desktop and
`display: none` below 1024px (the mobile drawer floats, so reserve no column).
Result: no color flash and no content shift on load. Applied across all 7
migrated examples (themed vs raw surface tokens per example).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the langgraph-python mobile-header fix to langgraph-js (same shared
canvas/header demo). Below 1024px the threads drawer's floating launcher is
fixed top-left and collided with the top-left CopilotKit header:
- max-lg:pl-24 on the header so the logo clears the launcher pill
- max-lg:pt-2.5 + pb-0 (and drop the logo span's pb-1.5 on mobile) so the
logo is vertically centered with the launcher + the Chat/App toggle
- trim the collapsed launcher's icon buttons (2rem -> 1.75rem) + tighter
padding so the pill height lines up with the toggle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On phones/tablets (≤1024px) the threads drawer's floating launcher is fixed
at the top-left corner, and it collided with this example's top-left
CopilotKit header. Three small fixes so the header reads as one tidy row:
- max-lg:pl-24 on the header so the logo clears the launcher pill
- max-lg:pt-2.5 + pb-0 (and drop the logo span's pb-1.5 on mobile) so the
logo is vertically centered with the launcher + the Chat/App toggle
(was sitting ~7px low)
- trim the collapsed launcher's icon buttons (2rem -> 1.75rem) + tighter
padding so the pill height (~36px) lines up with the toggle instead of
towering over it
Local to langgraph-python (the only rollout example with a top-left header).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Below 1024px the in-flow threads column squeezed the demo content and the
(full-screen) CopilotSidebar chat into slivers. Raise the responsive
breakpoint from 900→1024 and make the panel a true off-canvas overlay:
- collapsed → a small floating launcher pill pinned top-left (z-1300),
above the full-screen mobile chat, so threads stay reachable
- open → a fixed full-height panel sliding in from the left (z-1300),
content + chat use the full width behind it
- default the drawer to collapsed when innerWidth ≤ 1024 on mount
Applied across all 7 integration examples' shared threads-drawer, each
adapted to its own theme tokens (themed examples use --threads-drawer-*,
raw examples use --card/--border for the launcher surface).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Below 900px the drawer already overlays the content when open (existing media
query), but it defaulted to open — so on phones the 16rem panel covered the
content + chat on load. Default isOpen from window.innerWidth (> 900) so narrow
screens start at the 3.5rem rail; the user expands to the overlay on demand.
The drawer is client-mounted, so reading window in the initializer is safe.
Applied to the shared threads-drawer.tsx across all examples + north-star.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New thread (header + collapsed rail) now switches to a fresh threadId
(crypto.randomUUID()) instead of setting it to undefined. Setting threadId
undefined after a thread was selected did not reset the V2 chat — it kept
showing the previous conversation. A new id forces the configuration provider
to re-thread to an empty conversation.
- Collapsed-rail buttons use a native title tooltip instead of the styled
::after one, which clipped horizontally against the viewport's left edge in
the narrow rail. The wider expanded row keeps the styled tooltip.
Applied to the shared threads-drawer.tsx across all examples + north-star.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The threads-drawer had drifted into two near-identical .tsx variants (a 1-char
encoding diff) during the bespoke passes. Unify all examples onto a single
shared threads-drawer.tsx, which also propagates the mastra review fixes:
- collapsed rail: hover tooltips on the expand + new-thread buttons, and the
non-interactive decorative icon (read as a dead button) removed;
- tooltip renders below the trigger in each example's themed module.css (the
styled ::after tooltip was clipped when shown above the trigger).
Per-example theming (the module.css token values) is unchanged — only the
shared logic + the tooltip positioning rule are reconciled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Next route handlers only declared GET/POST, so the threads REST API's
DELETE (delete thread) and PATCH (archive/restore) were 405'd by Next before
reaching the runtime — thread deletion and archive were broken in every
example. Export PATCH + DELETE → handle(app) across all 7 routes.
mastra threads-drawer review fixes:
- tooltip renders below the trigger (the styled ::after tooltip was clipped by
the drawer's overflow containers when shown above);
- collapsed rail drops a non-interactive decorative icon (read as a dead button)
and adds hover tooltips to the expand + new-thread buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tokenize the last hardcoded literals in threads-drawer.module.css into --threads-*
CSS variables (with fallbacks to the existing design tokens / prior literals, so
the north-star is visually unchanged). Add THEME.md documenting the token contract
so each example can theme the drawer by defining tokens — no per-example CSS edits.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add src/components/threads-drawer/** to verbatimFiles (north-star is canonical).
Stage langgraph-js / langgraph-fastapi / strands-python in allowedDivergence for
the threads files (threads-drawer/**, src/app/page.tsx, next.config.ts) so parity
stays green while the frontend rolls out per batch; entries are removed as each
instance is synced. Verified: adds zero new parity failures (pre-existing
example-layout / docker-route-override / next-env.d.ts drift is unrelated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the threads-drawer components + locked-state gate, page.tsx wiring
(threadId state + CopilotChatConfigurationProvider + two-column shell), and the
next.config build-time derivation of NEXT_PUBLIC_COPILOTKIT_THREADS_ENABLED from
the license token. Without a license the gate renders the locked panel; the
drawer is client-mounted (SSR-safe). This is the canonical north-star frontend
that parity:sync distributes to instances per batch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docker-compose (postgres + redis + intelligence/composite) with its load-bearing
init-db SQL, a .env.intelligence fragment (appended on activation), and a README.
Framework-agnostic; consumed by copilotkit init -i / add-intelligence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When COPILOTKIT_LICENSE_TOKEN is set, wire CopilotKitIntelligence + identifyUser +
licenseToken (marker-fenced for opt-out); otherwise InMemoryAgentRunner — today's
behavior, unchanged. Document the optional Intelligence env keys (commented) in
.env.example. No visible change to the example until a license is provided.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auto-detect resolves the requested transport ("auto") to a concrete value
("rest"/"single") and writes it back to _runtimeTransport. setRuntimeTransport
then compared against that resolved value, so re-applying the same requested
mode — which the provider effect does on every render — compared unequal and
re-ran the entire /info handshake, rebuilding the runtime agents mid-session
and blanking the transcript (and any per-message UI bound to it).
Track the requested mode separately (_requestedTransport) and guard on it, so
re-applying an unchanged requested transport is a no-op.
Adds coverage: re-applying "auto" after auto-detect resolves it does not
refetch /info.
## Summary
- Select generated thread titles only from assistant text messages
returned by the naming run.
- Reject JSON-shaped title output unless it parses to an object with a
string title.
- Add Runtime regression coverage for tool-result suffixes and invalid
JSON title payloads.
## Validation
- pnpm nx run @copilotkit/runtime:test --
src/v2/runtime/__tests__/thread-names.test.ts
src/v2/runtime/__tests__/handle-run.test.ts (passed, 56 tests)
- pnpm nx run @copilotkit/runtime:build (passed)
- Pre-commit package checks passed
- pnpm nx run @copilotkit/runtime:check-types (blocked in dependency
task @copilotkit/shared:check-types: missing
LicenseContextValue/LicenseMode exports from
@copilotkit/license-verifier and telemetry index error)
- NODE_OPTIONS=--max-old-space-size=8192 pnpm nx run
@copilotkit/runtime:check-types --excludeTaskDependencies (failed: tsc
heap out of memory near 8 GB)
Display-only generative UI must use useComponent (not useFrontendTool): its
render is unconditional so the card persists after the tool call completes.
Also pass [transactions, cards] as deps so the renderer re-registers when the
data loads -- otherwise the closure captures the initial empty transactions
and the list renders empty.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The migration mounted the low-level CopilotKitProvider, which omits the
ThreadsProvider that holds the rendered message thread, so the agent ran but
the transcript stayed empty. Use the full CopilotKit provider, and set
useSingleEndpoint={false} to match the multi-endpoint Hono route (the default
single-endpoint transport 404s against it).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- cards PUT handler: stop console.info'ing the request body, which
included the plaintext PIN on every PIN-change request.
- wrapper.tsx: drop the duplicate ./globals.css import (already
imported in app/layout.tsx, which is the canonical place).
- copilot-context.tsx: replace window.location.pathname read during
render with usePathname() from next/navigation, matching how
components/layout.tsx already derives the current route.
- next.config.mjs: remove the eslint.ignoreDuringBuilds block — Next 16
no longer runs ESLint at build, and the key now produces an
"Unrecognized key(s): 'eslint'" warning. Confirmed warning is gone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
package.json was pinning next@14 / react@18, but the root pnpm.overrides
were already forcing next@16 / react@19 at install time and the code uses
Next 16 async params. Bump the declared ranges to ^16.0.10 / ^19 (and
@types/react{,-dom} to ^19) so the manifest matches reality and matches
the v2 reference demos (mcp-apps, generative-ui-playground).
Also drop examples/showcases/banking/pnpm-lock.yaml: the demo is a
workspace package (listed in root pnpm-workspace.yaml and resolved in the
root pnpm-lock.yaml), so the per-demo lockfile was vestigial v1 cruft
that contradicted the v2 migration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace v1-era hooks/data references with the actual v2 surface:
useAgentContext / useFrontendTool / useHumanInTheLoop from
@copilotkit/react-core/v2, the Hono runtime route, the seed.json + store
data layer, and the Northwind identity. Drop mentions of removed
SQL/MSA/ServiceNow/RAG features.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove the /Hydration/i pattern from the ignored-console-errors filter so
Next.js hydration mismatches surface as real failures, and add a
page.on("pageerror", ...) listener that records uncaught exceptions and
asserts none occurred. pageerrors are not filtered — any uncaught
exception fails the smoke test.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a CI-safe Playwright smoke test that verifies the banking showcase
boots and the CopilotKit v2 popup opens with its configured suggestion
pills, without invoking the agent (so it runs in CI without secrets).
Covers:
- Document title shows the Northwind Finance brand
- Credit-cards dashboard renders ("Credit Cards" heading)
- CopilotPopup launcher opens the dialog (Northwind Copilot)
- Three suggestion pills render: View transactions, Add a card,
Assign a policy
The dev server gets a dummy OPENAI_API_KEY so the runtime route boots,
but the test never sends a chat message or clicks a suggestion.
.gitignore: ignore test-results/, playwright-report/, and
tsconfig.tsbuildinfo so they don't become clutter.
Banking was on Tailwind v3, which made the pre-compiled v2 stylesheet
(`@copilotkit/react-core/v2/styles.css`, built as Tailwind v4 output)
incompatible with the PostCSS pipeline. A temporary inline-CSS hack in
`layout.tsx` worked around this. Migrate to Tailwind v4 to match the
canonical v2 demos and remove the hack.
- package.json: tailwindcss ^4, add @tailwindcss/postcss ^4, replace
tailwindcss-animate with tw-animate-css (v4 drop-in)
- postcss.config.mjs: switch plugin to @tailwindcss/postcss
- globals.css: use @import "tailwindcss" + @import "tw-animate-css";
add @custom-variant dark for the existing .dark/.light class toggle;
move shadcn color/radius mapping into @theme inline (preserves
bg-background/text-foreground/rounded-{lg,md,sm} semantics)
- tailwind.config.ts: deleted; theme now lives in CSS
- components.json: clear stale tailwind.config reference
- layout.tsx: drop the readFileSync/dangerouslySetInnerHTML inline-CSS
workaround; import "@copilotkit/react-core/v2/styles.css" the normal
way alongside ./globals.css
Verified: tsc clean, next build clean, Playwright-rendered all four
pages (/, /dashboard, /cards, /team) with computed-style checks
(sidebar bg-gray-900 dark, rounded-lg=8px, .border=1px) and opened
the copilot popup which renders fully styled with the v2 stylesheet.
## Summary
Follow-up to #5173 (bucket-(d)) closing three **pre-existing**
browser-pool concurrency defects on the non-release teardown paths. The
browser-pool is a context-pool over a fixed set of long-lived Chromium
processes; these defects biased the hygiene-recycle cadence and could
strand waiters / leak deferred recycles.
## Fixes (each red-green proven)
1. **`serveNextWaiter` orphan-close leaked `servedContexts`.** A waiter
timing out mid-`openContextOn` cleaned up the reservation/context but
never decremented `servedContexts` (which `openContextOn` had already
`++`'d) → every orphaned-by-timeout serve permanently inflated the count
→ premature hygiene recycles. Fix: decrement `servedContexts` in the
orphan-close block.
2. **Deferred `recyclePending` honored only on `release()`.** A recycle
deferred because `pendingOpens > 0` set `recyclePending`, but the
non-release teardown paths (orphan-by-recycle rollback, orphan-close)
returned the entry to idle without re-checking it → the deferred recycle
was dropped and the browser exceeded `recycleAfter` indefinitely. Fix:
shared `maybeFireDeferredRecycle(entry)` helper called on both
non-release teardown paths.
3. **`openContextOn` rollback didn't drain waiters.** The
orphan-by-recycle rollback freed a reservation but never
`scheduleServeNextWaiter()` → queued waiters could stall with free
capacity until an unrelated release. Fix: `scheduleServeNextWaiter()`
after the rollback.
## Verification
- Red-green for all three (reproduced each bug, then green).
- Full harness vitest: **1702–1705 passed**; browser-pool suite
**27/27** (mutation-tested — reverting fix#3 fails its guard). `tsc
--noEmit` exit 0.
- Public API + `BROWSER_POOL_MAX_CONTEXTS` default untouched.
## Reviewed
7-agent unbiased CR (cr-loop): the three fixes confirmed sound; one
ordering concern on the new code investigated and **refuted**
(sole-browser relaunch-failure rejects the waiter rather than stranding
it).
## Known further hardening (separate effort — NOT in this PR)
The CR surfaced additional **pre-existing** browser-pool reliability
bugs that warrant a dedicated hardening pass, independently flagged by
multiple reviewers:
- `shutdown()` vs in-flight `openContextOn` → leaked context (no
`isShutdown` re-check post-`newContext`); recycles added to
`inFlightRecycles` after shutdown's snapshot not awaited.
- crash-reason `recycleBrowser` abandons live contexts without
`.close()` — leaks on the non-dead `acquire`-retry path.
- `acquire` retry treats a transient `newContext` failure as a full
crash → recycles the whole browser, tearing down unrelated live contexts
(correlated flakiness).
- `launchChain` launch gate has no timeout → a single hung
`chromium.launch()` permanently deadlocks all relaunches.
- `parseInt` env parsing silently accepts trailing garbage
(`MAX_CONTEXTS=24x` → 24); no warning.
- context-close failures swallowed via bare `.catch(() => {})`
(inconsistent with `closeBrowser`'s logged path).
- relaunch-failure eviction only rejects waiters when the pool is fully
empty (doesn't redistribute onto surviving browsers); `pickLeastLoaded`
tie-break concentrates load on browser 0.
Bug 1: serveNextWaiter orphan-close (timed-out waiter mid-open) now mirrors
openContextOn's servedContexts++ with a decrement, so an orphaned serve no
longer permanently inflates servedContexts and biases the hygiene recycle to
fire early.
Bug 2: a hygiene recycle deferred via the release-path shouldRecycle&&hadWaiter
guard is now re-checked on the NON-release teardown paths (openContextOn
orphan-by-recycle rollback and serveNextWaiter orphan-close) via a shared
maybeFireDeferredRecycle helper, so the deferred recycle still fires when the
entry's last activity ends without a release() — previously it was dropped and
the browser exceeded recycleAfter indefinitely.
Bug 3: openContextOn's orphan-by-recycle rollback now calls
scheduleServeNextWaiter() so freed capacity immediately drains queued waiters
instead of stalling them until the next unrelated release/recycle handoff.
Adds three red-green regression tests (BUG1/BUG2/BUG3) to the browser-pool
suite. Public API and MAX_CONTEXTS default unchanged.
## Fixes
- **Gate LGP tool-rendering AAPL + Find-flights fixtures on `toolName`**
(not `hasToolResult`): the AAPL tool-rendering and Find-flights
first-leg fixtures now key off `toolName` so the right tool renders.
Local proof: tool-rendering AAPL + Find-flights run **local D6 green**.
- **Restore sandboxed-UI `jsFunctions` in `gen-ui-open-advanced`
fixtures** (agno, crewai-crews, langgraph-fastapi, langgraph-python,
mastra): the sandboxed Calculator/Ping `jsFunctions` were missing, so
the calculator never computed. Local proof: calc **`=` → 4
browser-verified**.
- **Resolve dashboard links to the real shell host via server-threaded
`shellUrl`**: the dashboard tree was entirely `"use client"`, so
`getRuntimeConfig()` returned the `ssr-placeholder.invalid` SSR sentinel
and baked dead hrefs into every Demo/Code link. `page.tsx` is now a
server component that reads the real host server-side and threads it
into the client `DashboardPage`. Local proof: **SSR links click → real
demo, verified**.
- **Fail `verify-deploy` on env-unset config sentinel + robust config
extractor**: when `SHELL_URL` is unset the server config returns the
`about:blank#shell-url-missing` sentinel; the deploy guard now fails
loud on it rather than shipping dead links, with a hardened config
extractor. Local proof: **deploy-guard red-green**.
- **Gate Coverage D6 badge + stats by the depth ladder; gated indicator
only on genuine lower-rung failure**: D6 is the top of the verification
ladder, so a green D6 claim is only valid when the ladder through D5 is
intact. New `d6Effective` collapses to gated (`—`) when a lower rung
genuinely fails (never on no-data), keeping the badge, stat, regression
flag, and chip in agreement. Local proof: **D6-gating full-suite 790
green incl dashboard-color-matrix 54/54**.
- **Raise browser-pool default `MAX_CONTEXTS` to 40 + correct pool
docs**: contexts (not chromium processes) are the scaling knob since the
PID ceiling of 1000 is the binding constraint; D6 peak 32 + D5 peak 8 =
40. Probe cadence/docs corrected to match. Local proof: **pool
MAX_CONTEXTS=40 locally proven, 50 PIDs ≪ 1000**.
## CR
Converged via 4 unbiased 7-agent cr-loop rounds + 2 fix rounds.
## Known follow-ups (not in this PR)
- **(d) browser-pool concurrency hardening** — `servedContexts`
inflation on `serveNextWaiter` orphan-close, `recyclePending` deferral
on non-release teardown, and waiter-drain on `openContextOn` rollback.
These are pre-existing pool internals; separate PR.
- **(b/c) minor cosmetic / naming items** — `DocsRow` unused `shellUrl`
prop; `computeColumnTallyDetail` labels a D6-absent amber as `"e2e"`;
the agno `gen-ui-open-advanced` `_meta` note is misleading but is the
SOLE source of agno Calculator/Ping fixtures (do NOT delete); `API=d3`
vs `d2` naming; `resolveD3` has no effective stale row (pre-existing);
`e2e-deep.yml` stale primary-key comment.
- **react-core consecutive-interrupt run-state fix** — a SEPARATE
pending branch; the `gen-ui-interrupt` cell needs it.