This repository has no description
flarebot docs bug-lessons.md
152 kB

Bug lessons #

2026-09-10 — Downward wheel did not resume a reader clamped to the bottom #

  • Affected area: ConversationContent scroll ownership in src/routes/Conversation.tsx.
  • Symptom signature: While native output streams, a viewport resize clamps a paused reader to the bottom. A downward wheel leaves Jump to latest visible and later output no longer follows.
  • Root cause: Following resumed only when a scroll event increased scrollTop and reached the bottom. After a layout clamp, a downward wheel need not increase scrollTop; the Chromium probe emitted only a one-pixel rounding correction in the opposite direction. Later output growth then left the reader behind.
  • Resolution: Treat an explicit positive wheel at the existing bottom as a request to resume through the existing jump path. Layout changes alone and upward gestures still preserve the paused reader's intent.
  • Regression signal: The packaged browser viewport-clamp case fails its Jump-hidden assertion before the fix and checks that resizing alone remains paused, then native downward wheel resumes following through the rest of the real stream. The original upward-reading case remains unchanged. This reproduces a concrete product defect; the earlier CI timeout's geometry was not captured, so that exact CI cause remains unconfirmed.
  • Prevention rule: When scroll policy depends on user intent, account for explicit gestures that cannot move an element already clamped to its boundary. Exercise native gestures after layout changes rather than assuming every gesture emits a positive scroll delta.

2026-09-10 — MCP credential dialog fell behind disabled actions #

  • Affected area: .mcp-portal in src/styles.css, credential replacement after Settings reconnect.
  • Symptom signature: The open credential dialog's Cancel button remains visible and enabled, but Playwright reports that the background Reconnect server button intercepts pointer events.
  • Root cause: MCP dialogs render inside the server row with no portal stacking level. Later disabled Kumo buttons use opacity, creating stacking contexts that paint over the dialog when their bounds overlap. Base UI's accessibility hiding does not fix that paint order.
  • Resolution: Give the MCP portal the same positioned stacking level as the existing task portal.
  • Regression signal: The native Settings browser fixture aligns the disabled Reconnect server button with Cancel after reconnect, verifies the dialog wins hit testing, and clicks Cancel. On the unchanged FLA42 assets, the same coordinate hit Reconnect server before the CSS rule and Cancel afterward.
  • Prevention rule: In-place modal portals need an explicit stacking level above background controls; verify pointer hit testing with overlapping disabled controls as well as focus and visibility.

2026-09-10 — Skill validation recovered unvalidated YAML metadata #

  • Affected area: worker/skill-package.ts, native Skill manifest projection.
  • Symptom signature: A Skill with metadata.__proto__ containing an object, cyclic YAML alias, oversized string or extra entry passes validation; serializing its native manifest can fail.
  • Root cause: Zod's record decoder skips an own __proto__ key. Reparsing raw frontmatter after validation restored that unvalidated value into the returned native manifest.
  • Resolution: Reject the reserved key before record decoding and construct the native manifest from validated metadata and the native parser's body. Preserve original input indices for errors and preserve BOMs during Unicode validation.
  • Regression signal: The native package suite rejects all four metadata bypass forms, reports the submitted file index, reads a BOM-bearing resource unchanged, and checks corrective error messages.
  • Prevention rule: Build accepted output from the validated projection; do not recover discarded fields by reparsing raw input. Test serialization and actual native consumers as well as validation success.

2026-09-10 — Bound normalized MCP endpoints before persistence #

  • Affected area: shared/mcp.ts, MCP connection creation and startup decoding.
  • Symptom signature: A short Unicode URL installs successfully, then listing/deletion fails and native restart rejects MCP storage.
  • Root cause: The input length check ran before URL.href percent-encoding expanded the path; persisted values exceeded the same schema's read-time length bound.
  • Resolution: Validate the normalized endpoint length before accepting it for storage.
  • Regression signal: pnpm test:mcp-model rejects the expanding Unicode endpoint and preserves the existing catalog.
  • Prevention rule: Apply storage bounds to normalized output as well as raw input; every accepted write must remain valid when decoded.

2026-09-10 — Miniflare restart fixtures need the current persistence option #

  • Affected area: tests/mcp-model.test.mjs, Miniflare 5 compatibility conversion.
  • Symptom signature: Native SQLite CRUD passes, but a fresh Miniflare instance sees an empty MCP catalog after disposal despite a configured durableObjectsPersist directory.
  • Root cause: The pinned convertV4MiniflareOptions drops the old durableObjectsPersist option. With shared storage disabled, the runtime derives isolated persistence from resourcePersistencePath, overriding a separately supplied isolated path.
  • Resolution: Set resourcePersistencePath to the test's temporary directory and reuse it across instances.
  • Regression signal: pnpm test:mcp-model fails on the first restored catalog read with the old option and passes with the canonical persistence root.
  • Prevention rule: Verify persistence with full runtime disposal/recreation and the pinned Miniflare option schema; repeated reads in one instance do not establish restart durability.

Durable, evidence-backed lessons from debugging sessions in this repo. Symptom-match new bug reports against these entries before theorising.

2026-09-09 — Mobile chat input focus zoom #

  • Affected area: .chat-composer textarea in src/styles.css.
  • Symptom signature: Focusing the chat message input zooms the page on mobile.
  • Finding: The focused input computed to 14px, below Safari's 16px focus-zoom threshold. The viewport already permits normal user zoom; no restrictive viewport override is needed.
  • Resolution: Use 16px for the composer on narrow screens and coarse-pointer devices, covering landscape phones as well as portrait. Retain 14px on wide fine-pointer layouts and preserve pinch-to-zoom.
  • Regression signal: FLAREBOT_CHAT_CASE='mobile composer' pnpm test:chat-ui failed with 14px !== 16px before the CSS change. It checks focused portrait and touch-landscape font sizes, desktop sizing, and the absence of zoom-disabling viewport settings against built assets.
  • Validation limit: Chromium checks the CSS prerequisite, not actual iOS keyboard focus zoom. Confirm the focus behaviour on a real iPhone after deployment.
  • Prevention rule: Keep mobile editable text at least 16px; do not suppress user zoom to work around input focus zoom. Treat desktop device emulation as layout coverage, not an iOS keyboard reproduction.

2026-09-08 — iOS keyboard bypassed the model dialog's overflow lock #

  • Symptom signature: Model scrolling works with the iOS keyboard hidden, but gestures pan the background while the search input has the keyboard open.
  • Root cause: The dialog's Base UI scroll lock only hides document overflow on iOS. That does not contain the visual viewport when the software keyboard covers part of the layout viewport.
  • Initial mitigation: Use the existing Octane Aria usePreventScroll hook while the model picker is open. Its iOS path handles touch containment and input focus; cleanup restores page scrolling and native focus. The user subsequently confirmed that the keyboard-open failure persisted with this code deployed.
  • Regression signal: A browser test selects the iOS platform path, focuses search, and reduces/pans the visual viewport without resizing the layout viewport. The current code fails touch cancellation outside the list; the test also checks that the list allows native touch scrolling and dismissal restores the lock state. This simulates the event policy, not a physical iOS keyboard.
  • Prevention rule: Resizing a desktop viewport and dispatching touch swipes does not reproduce an iOS software keyboard. Verify the platform-specific scroll lock as well as list overflow, and retain a physical-device validation step for keyboard behaviour.

2026-09-09 — Dismiss the iOS search keyboard when browsing model results #

  • Symptom signature: The deployed scroll lock still permits background scrolling on iOS with the search keyboard open. The user confirmed that the same model list scrolls correctly when the keyboard is hidden, and desktop is unaffected.
  • Mitigation: On a single-finger touch in iOS model results, move focus from the search input to the dialog. This dismisses the keyboard without closing the picker, clearing the filter, cancelling native touch scrolling, or allowing the focus trap to reopen the keyboard. Desktop and multi-touch gestures are unchanged.
  • Regression signal: The browser test fails the keyboard-dismissal assertion before the change and verifies focus transfer, retained filtering, and the ability to focus search again. Existing wheel/touch tests cover the native list scrolling path.
  • Validation limit: These browser tests verify interaction policy; real iOS keyboard animation and scroll handoff still require an iPhone check after deployment. Do not call a synthetic keyboard test a device reproduction.

2026-09-08 — Domain fixtures inherited the publisher's production route #

  • Symptom signature: Domain Workflow tests receive 403 instead of unauthenticated 401, and the domain UI test times out finding the Hostname field.
  • Root cause: Both localhost fixtures copied production routes into their Wrangler configuration. Miniflare rewrote the request host, so the publisher's exact-origin gate rejected requests before authentication or page rendering.
  • Resolution: Omit production routes and workers_dev from both local fixture configurations, matching the OAuth and ownership fixtures. Assert the setup page's HTTP status before interacting with it.
  • Regression signal: The Workflow test reproduced the CI 403 locally before the change. Run CI=true pnpm test:domains to verify the complete domain path.
  • Prevention rule: When adding deployment routing, audit every test that copies the production Wrangler configuration. Keep local request origins aligned without relaxing production origin checks.

2026-09-08 — Model picker trapped scroll gestures inside its listbox #

  • Symptom signature: The model picker opens, but desktop wheel and mobile touch scrolling cannot reach the lower models.
  • Root cause: The inner listbox had overflow-y: auto and scroll containment, while its constrained parent was the actual scrolling element. The inner element trapped gestures before they could scroll that parent.
  • Resolution: Keep scroll containment on CommandPalette.List and remove the nested listbox overflow rules.
  • Regression signal: The composer browser test failed with the last option below the viewport before the fix. It now reaches the final option with desktop wheel input and mobile touch swipes, including a shortened keyboard viewport.
  • Prevention rule: Test actual wheel and touch input; locator clicks can scroll elements automatically and conceal this failure. Match deployed assets to their source branch before reproducing a live UI report.

2026-09-06 — Public Worker-to-Worker requests require explicit routing #

  • Affected area: Publisher readiness fetch and customer-to-publisher login bridge; both Wrangler compatibility flag lists.
  • Symptom signature: Worker and Sandbox provisioning completed, but verification failed immediately. The signed customer health endpoint passed from an external client, while the same request from a cloud Worker returned HTTP 404 with Cloudflare error 1042.
  • Root cause: Neither Worker enabled global_fetch_strictly_public. Native fetch could not use the other Worker's public workers.dev endpoint. This routing flag is not implied by the compatibility date or nodejs_compat.
  • Resolution: Enable the flag on both publisher and customer deployments. Publish the changed customer contract as 0.1.0-dev.2, retaining the original dev.1 archive and an explicit forward-upgrade edge rather than modifying its immutable bytes.
  • Regression signal: The same signed cloud-Worker probe returned error 1042 before the flag and full readiness HTTP 200 after it. Configuration/artifact tests require the new release's flag and retain support for the exact legacy flag list needed to recover and upgrade existing installations.
  • Prevention rule: Test public Worker-to-Worker calls from a deployed Worker in each direction. A successful external HTTP request or local service fixture does not certify Cloudflare's edge routing; maintain explicit routing requirements in both deployment contracts.

2026-09-06 — Native fetch rejected the deployment client's receiver #

  • Affected area: control-plane/deployment-api.ts, account preparation and uploaded Worker verification.
  • Symptom signature: Installation stayed at “Preparing your account”; the native Workflow exhausted four immediate temporarily_unavailable attempts in resolve customer origin, before deploying resources.
  • Root cause: The adapter stored ambient Workers fetch on the client and invoked this.network(...). Native fetch rejected the DeploymentAPI receiver with “Illegal invocation” before sending a request; the adapter sanitized that exception. Arrow-function provider fixtures did not enforce the native receiver constraint.
  • Resolution: Call the injected transport as a standalone function in both the JSON request and multipart content verification paths. Preserve the fixed endpoints, credentials, bounds and error classifications.
  • Regression signal: pnpm test:deployment-network exercises the actual adapter and native Workers fetch, replacing only outbound service responses. Before the fix, account preparation fails without reaching the fixture endpoint; the corrected transport resolves the origin and verifies uploaded content.
  • Prevention rule: Tests for an injected platform primitive must preserve its native calling convention. Exercise default transports in the target runtime as well as arrow-function substitutes; receiver-sensitive APIs cannot safely be invoked through arbitrary owning objects.

2026-09-06 — Cloudflare callback scope metadata rejected valid sign-ins #

  • Affected area: control-plane/http.ts, OAuth callback query validation.
  • Symptom signature: Every real Cloudflare sign-in returned /connect?error=oauth_invalid_callback and “This sign-in link is invalid or expired. Connect to Cloudflare again.” even with a fresh transaction.
  • Root cause: Cloudflare returned code, scope, and state; the callback allowlist rejected scope before claiming the transaction or exchanging the code. Both the direct and browser fixtures had omitted this provider field.
  • Resolution: Accept a single callback scope parameter as non-authoritative metadata. Continue validating granted scopes from the token exchange and retaining state, cookie, issuer, duplicate-query, expiry and one-use checks.
  • Regression signal: pnpm test:oauth reproduces the exact error before the fix and passes afterwards through the native Worker/DO callback and Chromium navigation. It also rejects duplicate scope parameters and a token response missing permissions despite a complete callback scope list.
  • Prevention rule: Model the real provider's callback shape in both HTTP and browser fixtures; distinguish provider metadata from verified token grants. Testing OAuth endpoints separately does not validate the application's complete callback path.

2026-09-06 — Local runtime had no owner login path #

  • Affected area: worker/bridge.ts, local customer-runtime setup
  • Symptom signature: Opening http://localhost:8787/auth/login returned Owner authentication or installation verification failed. and the shell remained disconnected when using .dev.vars.example.
  • Root cause: The development template intentionally omitted production bridge keys, while the only owner-session issuer required a configured control-plane bridge.
  • Resolution: An explicit development runtime on a loopback origin issues its local owner session from /auth/login; non-loopback and production installations retain the signed OAuth bridge.
  • Regression signal: pnpm test:runtime asserts that the actual packaged local Worker redirects /auth/login, sets the hardened owner cookie, and accepts it on the private status route.
  • Prevention rule: Every documented local startup path that exposes authenticated UI must include a bounded way to establish its development identity.

2026-09-05 — Sidebar SSR flash "window is not defined" #

  • Affected area: octane-kumo Sidebar.Provider → useIsMobile (packages/octane-kumo/src/components/sidebar/sidebar.tsx); surfaced in flarebot's RootLayout, which renders Sidebar.Provider.
  • Symptom signature: First paint shows the root error boundary ("Something went wrong! window is not defined"), replaced by the real shell after hydration. The SSR HTML of every route contains the error text.
  • Root cause: useIsMobile's useSyncExternalStore snapshot called unguarded window.matchMedia. Octane's server useSyncExternalStore falls back to getSnapshot() when the compiled call carries no slot arg — and the server hook-slot transform wraps the nested useIsMobile() call site with withSlot but does not inject slots into its body (verified in dist/server/entry.js: 3-arg call, no slot; the client bundle for the same source has the slot). So SSR threw ReferenceError: window is not defined.
  • Resolution: Guarded getSnapshot with typeof window === "undefined" ? false : window.matchMedia(query).matches, matching getServerSnapshot. Fixed in octane-kumo, consumed via the link: dependency.
  • Regression signal: packages/octane-kumo/tests/sidebar-ssr.test.tsx — SSR-renders Sidebar.Provider with window stubbed to undefined; fails pre-fix, passes post-fix. Manual loop: start pnpm dev, then curl -s http://localhost:5176/about | grep -c "window is not defined" must print 0.
  • Prevention rule: Every useSyncExternalStore getSnapshot — and any render-path browser-global access — in Octane-ported components must be SSR-safe (typeof window/document guards). Never rely on getServerSnapshot alone; the server transform may drop it.

2026-09-05 — Native facet client never becomes ready #

  • Affected area: AgentClient.basePath / PartySocket URL construction.
  • Symptom signature: Authenticated facet history works, but client.ready remains pending with no identity frames and WebSocket handshakes return 404.
  • Root cause: Passing /agents/... as basePath generates ws://host//agents/...; PartySocket prepends its own slash.
  • Resolution: Pass the authenticated pathname with its first slash removed to basePath; preserve the slash for HTTP requests.
  • Regression signal: pnpm test:think awaits bounded native facet readiness and completes streamed turns using basePath: pathFor(id).slice(1).
  • Prevention rule: Treat a PartySocket base path separately from an absolute HTTP pathname. Do not loosen the server's exact route guard for malformed URLs.

2026-09-06 — Tool activity recovery and clear use different native paths #

  • Affected area: worker/tool-activity.ts, Think tool hooks and native clear.
  • Symptom signature: An interrupted call recovers with the same tool-call ID, but a blanket terminal-state guard leaves its activity failed while the native transcript succeeds. A public clearMessages override alone also misses WebSocket clear because Think invokes its own clear handler.
  • Root cause: An interrupted observation has an unknown outcome, unlike a known tool failure or explicit cancellation. Think 0.17's two clear paths both call the documented protected resetTurnState, but WebSocket clear does not dispatch through public clearMessages.
  • Resolution: Permit only interrupted observations to reopen on authoritative execution/result evidence, preserve first timestamps and count observed attempts. Strip activity presentation metadata synchronously at the native reset seam, retaining ID-only tombstones against late callbacks.
  • Regression signal: pnpm test:activities restarts real workerd during a tool, reissues that exact native call ID and compares transcript/activity success; it also clears native chat while an abort-ignoring tool is running and checks that a later turn survives without restored old summaries.
  • Prevention rule: Distinguish known terminal outcomes from interrupted observations, and verify both native HTTP/RPC and WebSocket lifecycle paths before choosing an override. Tests must actually reissue the same call ID to exercise replay, rather than merely letting the recovered model finish text.

2026-09-06 — Think fetch timeout stopped after response headers #

  • Affected area: Think 0.17.0 dist/tools/fetch.js, executeRequest.
  • Symptom signature: A server returns headers and an initial body chunk, then stalls; native timeout and caller cancellation no longer interrupt the body.
  • Root cause: Returning finalizeResponse(...) without awaiting it exits the surrounding try/finally immediately, clearing the request timer and removing caller abort forwarding before readCapped finishes.
  • Resolution: Version-pinned pnpm patch adds await at that return. Native limits, redirect filtering and download code remain authoritative.
  • Regression signal: pnpm test:web feeds a controlled slow body to the actual native fetch tool and verifies timeout plus underlying signal abort, then stops a native Think read_url invocation during body consumption.
  • Prevention rule: When cleanup releases cancellation or resource ownership, await asynchronous body processing before leaving the protected scope. Retest slow bodies before removing the patch on a Think upgrade.

2026-09-06 — Browser acquisition outlived its conversation facet #

  • Affected area: native facet deletion and Browser Run session acquisition.
  • Symptom signature: Deleting a conversation while browser creation was awaiting its response left the actual remote session open. The child's late continuation logged Facet was deleted; capturing a parent RPC stub alone did not keep that continuation alive.
  • Root cause: deleteSubAgent destroys child execution and its pending continuations. A child-owned finally or waitUntil cannot guarantee cleanup after the facet itself is destroyed.
  • Resolution: The surviving parent owns the native create request and its waitUntil, records the returned ID before connection, and rechecks whether the conversation still exists. A late result for a deleted conversation is closed directly. The child still owns only its session's browser commands.
  • Regression signal: pnpm test:browser delays a real local Chromium create reply, deletes the conversation, and probes that exact session for HTTP 404.
  • Prevention rule: Own external acquisition in a lifetime that survives its caller's deletion. Cleanup intent must survive alongside that owner; remote creation whose ID is lost still requires an honest service-expiry fallback.

2026-09-06 — Stable Sandbox session buffering defeated output limits #

  • Affected area: Sandbox 0.12.9 getSandbox and shell streaming.
  • Symptom signature: Direct subclass execStream buffered an unterminated output line until the deadline; explicit exit ended the SDK's persistent session without an ordinary command completion event.
  • Root cause: getSandbox(..., { enableDefaultSession: false }) implements stateless execution in its public helper wrapper. Calling this.execStream inside a subclass bypasses that wrapper and uses a persistent shell session.
  • Resolution: The one-use Sandbox lifecycle wrapper delegates through the public stateless helper, verifies its own namespace identity, and preserves native SSE byte chunks and terminal exit codes.
  • Regression signal: pnpm test:shell checks nonzero exit and floods real Docker stdout without newlines; the byte cap must destroy the container before its time deadline. It also checks partial-output cancellation.
  • Prevention rule: Test exact streaming semantics with unterminated output, explicit exit and silence. Wrapper options are not necessarily stored runtime configuration; retain the SDK helper that implements them.

2026-09-06 — Native one-shot cleanup retry deduplication #

  • Affected area: Agent native scheduled resource cleanup callbacks.
  • Symptom signature: A failed cleanup schedules an idempotent retry with identical type, callback and payload; no later retry occurs.
  • Root cause: Native scheduling deduplicates against the currently executing one-shot row, then deletes that row after the callback returns.
  • Resolution: Cleanup retries create a new native one-shot schedule without self-deduplication; confirmed cleanup cancels remaining matching schedules.
  • Regression signal: pnpm test:shell injects four consecutive destroy failures and waits for actual native scheduled cleanup and stopped container.
  • Prevention rule: Test successive failures, not only the first retry, and account for scheduler row advancement when rearming inside a callback.

2026-09-06 — Local public container images still trigger Wrangler auth #

  • Affected area: Credentials-free native Worker and Docker tests.
  • Symptom signature: CI fails before starting a local Worker with an account or token error, although local tests pass with a developer login.
  • Root cause: Wrangler 4.128 normalizes container image references and fills registry API authentication even when local container execution is disabled.
  • Resolution: The shared test fixture supplies a synthetic account/token and redirects Cloudflare API requests to closed loopback. Optional registry login fails locally; Docker pulls the pinned public image directly. Production deployment configuration stays account-neutral.
  • Regression signal: Worker and real Docker shell tests pass with an isolated Wrangler config directory and no real Cloudflare credentials or API access.
  • Prevention rule: Verify native local tests without developer authentication; disabling remote execution does not necessarily disable config-time auth.

2026-09-06 — Chat UI artifacts and failed-case cleanup were host-dependent #

  • Affected area: tests/chat-ui.test.mjs screenshot capture and native client lifetime.
  • Symptom signature: Unprivileged Linux reports EACCES: permission denied, mkdir '/private' in the rich-message case; the later history case times out locating a desktop sidebar link. A failed clear case can leave the process alive after TAP has reported its assertion.
  • Root cause: Screenshots used a developer's macOS path. Its failure skipped viewport restoration, contaminating later cases. A native leaf client closed only on success kept reconnecting after failed assertions; held HTTP gates and route handlers also lacked failure cleanup.
  • Resolution: Store screenshots beneath the test-owned temporary directory (or explicit FLAREBOT_CHAT_SCREENSHOTS), restore viewport and release case resources in finally, and register bounded, idempotent suite cleanup. Use TAP output so the original assertion is visible immediately.
  • Regression signal: pnpm test:chat-ui in an unprivileged Linux container exercises the same screenshot and desktop-navigation sequence. Injecting an assertion after the clear case opens its client and holds HTTP made the old harness hit an external 25-second timeout; the corrected harness reports the intentional failure and exits nonzero normally in about three seconds. A separate pending-body injection exits after its 10-second parent timeout, confirming cleanup is independent of the suspended test body.
  • Prevention rule: Derive test artifact paths from tmpdir() or an explicit caller path. Register resource cleanup before awaiting readiness, and verify failed assertions release native reconnecting clients as well as browsers.

2026-09-06 — Completed resume observer overlaid authoritative history #

  • Affected area: ConversationSession native resume ownership and terminal history.
  • Symptom signature: After full Worker restart, native history contains a partial assistant and one separate continuation, but the connected UI also appends the continuation to the earlier partial. Native IDs match while text remains duplicated, even after waiting.
  • Root cause: An unsolicited fallback observer could become transport-owned during resume. Owned terminal frames bypassed the broadcast state transition, leaving its accumulator observing after the stream finished. The final fresh HTTP history was then overlaid with that obsolete accumulator.
  • Resolution: Retire the observer only when its stream ID matches the completed native request, following the SDK's own owned-response handling. Preserve native partial and continuation rows and let final history replace their text without an obsolete overlay.
  • Regression signal: pnpm test:chat-ui compares each rendered message's ID and text parts against native history at Connected after a real Worker restart, in addition to requiring the continuation text exactly once. The Linux failure reproduced with fresh history revision unchanged and an obsolete observer; the native mock had made only one continuation call.
  • Prevention rule: When streaming ownership changes, terminal cleanup must retire both ownership paths for that request. Compare authoritative message content as well as IDs, and never clear a different active stream's observer.

2026-09-06 — Unrouting raced an intercepted history response #

  • Affected area: Clear and stale-selection HTTP gates in tests/chat-ui.test.mjs.
  • Symptom signature: Linux CI fails Clear with route.fulfill: Route is already handled!; parent cancellation then produces closed-page cleanup errors. The same gate can pass on another runner.
  • Root cause: UI readiness did not mean every intercepted history handler had finished. A later non-aborted handler was still awaiting route.fetch when page.unroute disabled interception; its subsequent fulfillment raced Chromium's handling of that request.
  • Resolution: Both held-history cases use the public page.unrouteAll({ behavior: "wait" }) within the existing deadline. Active handlers finish before interception is disabled; route errors are not ignored on the success path and stale-history assertions remain intact.
  • Regression signal: The actual Clear case in an unprivileged Linux container reproduced the exact error with delayed fulfillment. Changing only route teardown to await handlers made that same narrowed case pass and exit normally. pnpm test:chat-ui retains both held-history acceptance cases.
  • Prevention rule: Await routing work itself before removing interception. A rendered ready state and release of a gate do not prove its asynchronous callback has finished.

2026-09-06 — Shell reconnect selector matched the conversation control #

  • Affected area: Session-expiry recovery in tests/app-shell.test.mjs.
  • Symptom signature: Playwright reports two matching Reconnect buttons after both shell and conversation connections learn that the session expired.
  • Root cause: The shell test used a page-wide action selector. Timing could expose either one or both independently owned reconnect controls.
  • Resolution: Scope shell actions to .connection-notice. The signed-session expiry case establishes both connections, awaits their native expiry, then verifies only shell Reconnect restores both. Cookie removal remains a separate new-request authorization check: it does not revoke an accepted native socket.
  • Regression signal: pnpm test:app-shell reproduced the original strict-mode failure. Preserving the chat socket while delivering offline/online events also reproduced the mistaken expectation that cookie removal must close chat. The real signed-expiry regression passes without relying on network disconnect timing.
  • Prevention rule: Scope repeated actions to their component, and trigger the actual authorization event before expecting an established socket to expire.

2026-09-06 — Held task page exceeded the native RPC deadline #

  • Affected area: Older-history repair case in tests/tasks-ui.test.mjs.
  • Symptom signature: Captured older page appended: 25, with the UI showing Could not load older runs. Try again. on slower CI runs.
  • Root cause: The test withheld a real older-page response until 29 native executions finished. That wait could exceed the browser client's 10-second RPC deadline, so the correctly captured four-row page arrived after its request failed.
  • Resolution: Pause the browser clock only during that deliberate response hold, leaving Worker execution in real time, and resume in finally. Assert the captured task, older cursor, four rows and active native status before release.
  • Regression signal: The real native case with a 12-second execution delay reproduced the exact failure and passed with controlled browser time. The test retains that delay, all 29-row append and completed-status reconciliation checks.
  • Prevention rule: Deliberate transport holds must control the client's deadline independently of slow server work when the assertion concerns stale data, not timeout.

2026-09-06 — Conversation socket outlived the browser's network loss #

  • Affected area: ConversationSession in src/runtime/conversation-session.ts; surfaced in the session-expiry recovery case of tests/app-shell.test.mjs.
  • Symptom signature: After setOffline(true), cleared cookies and setOffline(false), the shell reaches Sign-in required while the conversation heading still reads Connected and never shows Sign in to this installation to view this conversation.
  • Root cause: The shell drops its socket on the browser's offline event, but the conversation only reacted to a WebSocket close. Chromium's offline emulation (and a real loss on some networks) does not reliably close an established socket, so the conversation kept trusting a stale connection and never re-read history, which is where the 401 would have been observed.
  • Resolution: ConversationSession now listens to offline/online: offline detaches immediately and shows the reconnecting notice; online reconnects (queued if an aborted connection is still unwinding). Terminal states are left alone.
  • Regression signal: CI at head 54d813e kept the conversation Connected after a brief offline toggle and cookie removal. pnpm test:chat-ui covers offline stream recovery; pnpm test:app-shell separately verifies automatic recovery after both connections observe actual signed-session expiry.
  • Prevention rule: Every independently owned connection must observe the same browser network signals; a live socket is not evidence that the session is valid.

2026-09-06 — Native pending task status rejected by a test assertion #

  • Affected area: Captured older-page assertion in tests/tasks-ui.test.mjs.
  • Symptom signature: The captured real older page includes active runs fails after the correct four-row older page is captured during execution.
  • Root cause: The assertion used queued, but the task domain calls an acknowledged, unfinished submission pending. Faster native acknowledgement changed the captured rows from dispatching to valid pending.
  • Resolution: Check the actual active statuses: dispatching, pending, and running; retain the cursor, row-count and eventual completion checks.
  • Regression signal: CI rejected the captured older page; a local native capture confirmed real pending rows. The full task UI gate retains its active page assertion and all 29-row append/completion checks with the documented value.
  • Prevention rule: Read protocol and domain status values from their source; similar natural-language descriptions are not interchangeable enum values.

2026-09-06 — Referrer policy changed OAuth form Origin #

  • Affected area: Public OAuth onboarding forms and exact-Origin CSRF checks.
  • Symptom signature: Chromium submitted the real Connect form with Origin: null, so the Worker correctly rejected the request with HTTP 403.
  • Root cause: Applying Referrer-Policy: no-referrer to the public document also affected the Origin header on navigation form POSTs.
  • Resolution: Public onboarding uses same-origin, which retains same-origin form verification without disclosing referrers to Cloudflare. Callback and API responses retain no-referrer; exact-Origin checks remain unchanged.
  • Regression signal: pnpm test:oauth exercises actual Chromium form POSTs, redirect-chain provider interception, callback cookies, account selection and logout against the real Worker and native Durable Objects.
  • Prevention rule: Test browser navigation forms as well as direct HTTP API calls. Never weaken Origin validation to accommodate a referrer-policy mistake.

2026-09-06 — Native Workflow error messages are not a result protocol #

  • Affected area: control-plane/installation-workflow.ts
  • Symptom signature: Native health_failed and resource_conflict step failures became temporarily_unavailable in installation metadata, despite the correct callback error appearing in local logs.
  • Root cause: The Workflow/RPC boundary changes nonretryable error messages; matching an exact application enum against the wrapped message loses the original classification.
  • Resolution: Nonretryable callback failures return a strict safe result containing the enum. The Workflow interprets that persisted result outside the callback. Only transient failures throw a sanitized error for native retries.
  • Regression signal: pnpm test:orchestrator checks exact durable failure codes through real local Workflow execution and confirms a failed boot never assigns an installed release.
  • Prevention rule: Persist declared, nonsecret step results for domain failures. Do not depend on native exception message formatting as an application protocol.

2026-09-06 — Deployment fixtures must exercise the runtime configuration consumer #

  • Affected area: control-plane/deployment-config.ts, customer installation configuration and the orchestrator fixture.
  • Symptom signature: The deployment adapter included bridge.issuer, while the finished customer schema accepted only keyId and publicKey. Independent orchestrator and login fixtures passed, but the production customer loader rejected the actual uploaded variables.
  • Root cause: The producer retained an earlier interface proposal and the orchestration fixture verified multipart metadata without invoking its real consumer.
  • Resolution: Deployment configuration now passes through the production installation parser after origin resolution. The native provider fixture also feeds the exact uploaded variables to loadCustomerConfig and loadCustomerSecrets, using a fixed HTTPS publisher origin.
  • Regression signal: pnpm test:orchestrator validates the actual multipart bindings with the production customer loaders before accepting a Worker upload.
  • Prevention rule: Independently valid subsystem fixtures do not establish integration. Exercise the real consumer against the exact serialized producer output, including production origin and secret-binding requirements.

2026-09-06 — Upgrade preflight rejected native Worker metadata #

  • Affected area: control-plane/deployment-api.ts, upgrade observation and metadata preservation.
  • Symptom signature: A Ready installation rejects its first upgrade with resource_conflict before any upload, despite unchanged code, configuration and namespace identities.
  • Root cause: The provider fixture omitted default tags, version annotations, and script_runtime.assets/containers. The strict observation allowlist therefore classified ordinary Cloudflare upload results as resource drift.
  • Resolution: Preserve Worker tags and writable message/tag annotations, excluding only read-only workers/triggered_by provenance. Validate the exact asset-routing defaults and installation-owned container mapping produced by Flarebot's upload. Unknown metadata and changed routing/ownership still fail closed.
  • Regression signal: pnpm test:orchestrator models the native response fields, checks preserved customer metadata through upgrade/recovery, and rejects unsupported changes before customer mutation. The read-only live DeploymentAPI.baseline reproduction must also pass against the original installation.
  • Prevention rule: Build upgrade fixtures from the full native upload/read response shape. Separate writable metadata, server-generated provenance and deployment-owned settings explicitly; never fix an allowlist mismatch by dropping all unfamiliar fields.

2026-09-06 — Status-read deadline aborted valid upgrade commands #

  • Affected area: control-plane/ui/installation-status.ts, installation command requests.
  • Symptom signature: Continuing a saved upgrade displays “Flarebot could not be reached” while retaining the previous Ready release; ordinary status reads still succeed.
  • Root cause: Commands shared the 15-second polling deadline even though upgrade/recovery performs synchronous Cloudflare resource and code verification before acknowledgement. A native read-only preflight against the deployed artifacts took 15.2 seconds. A genuine accepted upgrade response delayed 16 seconds reproduced the exact browser alert; the original user's failed POST was not captured.
  • Resolution: Allow installation commands 120 seconds while retaining the 15-second read deadline, explicit cancellation, frozen request identity and replay behavior.
  • Regression signal: pnpm test:installation-status holds the real saved-upgrade 202 response for 16 seconds, rejects a false network alert, and verifies the exact request and final installed release. Existing deliberate network-loss and recovery cases remain covered.
  • Prevention rule: Budget acknowledgement time for the synchronous work an endpoint performs. A status-read timeout is not automatically suitable for a command that verifies remote resources before starting durable background work.

2026-09-06 — Live missing-Workflow errors blocked startup recovery #

  • Affected area: control-plane/start-installation.ts, recovery after the registry saves an operation but before its Workflow is created.
  • Symptom signature: Installation stays Updating / Preparing with no matching native Workflow; continuing the saved recovery request returns HTTP 503 before any Worker upload.
  • Root cause: Local native Workflow.get throws instance.not_found, while the deployed binding throws (instance.not_found) Instance not found. Recovery recognized only the local message and its RPC prefix, so the live absence signal became temporarily_unavailable.
  • Resolution: Recognize the observed deployed absence message alongside the existing local forms. All other lookup/termination failures remain unavailable and cannot authorize replacement execution.
  • Regression signal: pnpm test:orchestrator exercises the real recovery endpoint through a missing native instance with the deployed error serialization and verifies that unknown lookup errors leave the saved operation and provider resources unchanged. A protected read-only live probe confirmed both the missing execution and exact remote error shape.
  • Prevention rule: Native service error serialization can differ between local and deployed runtimes. Capture the actual remote contract at a failing boundary and cover its known representation explicitly; never interpret every lookup failure as absence.

2026-09-06 — Script PUT rejected version-pinned inheritance #

  • Affected area: control-plane/deployment-api.ts, upgraded Worker upload and source-version checks.
  • Symptom signature: Upgrade upload records its intent, fails with temporarily_unavailable, then stops with recovery_required while dev.1 still serves. The live PUT returns HTTP 400 / code 10057: inherited version_id accepts only the literal latest.
  • Root cause: The fixture accepted concrete version UUIDs from the shared API schema, but the deployed script PUT endpoint rejects them for every inherited binding.
  • Resolution: Use strict inheritance with version_id: "latest". Require the newest uploaded version to equal the checked active source during preflight and again immediately before PUT, including undeployed uploads. Preserve post-upload fingerprints; concurrent external uploads remain a documented non-atomic boundary.
  • Regression signal: pnpm test:orchestrator rejects UUID inheritance, checks latest-version reads around staging, and blocks both preexisting and late newer uploads before PUT. The guarded real dev.2 upload changed from HTTP 400 to success with the same saved configuration and resources.
  • Prevention rule: Exercise the actual provider endpoint when its behavior differs from a shared schema. Do not replace a pinned source with latest without checking all uploaded versions, and do not describe the resulting check/write sequence as atomic.

Upgrade operation defaults belong only at storage boundaries #

The native upgrade gate exposed a baseline-erasure bug: using a defaulted Zod storage schema with .partial() for intent mutations inserted upgrade: null and rolloutId: null into otherwise unrelated Worker-intent changes. The upload and health completed, but request replay and failed-upgrade retry lost their immutable source baseline. Use an explicit strict mutation schema with no storage defaults, excluding the immutable upgrade baseline entirely. Native replay/retry tests and an unknown-field intent rejection protect this boundary.

A ready installation is not evidence that an unsubmitted upgrade succeeded. The real browser test aborted the upgrade request before the registry saw it, then reloaded against the still-ready old installation. Reusing the initial-install ready cleanup erased the frozen upgrade target. Only clear an upgrade intent from a ready read when the installed identity matches that exact pending target; otherwise retain it for explicit replay.

2026-09-06 — Preserve request provenance in native fixture failures #

  • Affected area: JSON response reads in tests/orchestrator.test.mjs.
  • Symptom signature: Linux CI's upgrade process-restart case failed with Unexpected token 'E', "Error: Net"... is not valid JSON; the Undici-only stack did not identify the request or HTTP status.
  • Root cause: Bare response .json() failures discarded request provenance. The underlying intermittent transport/restart cause remains unconfirmed; focused local/Linux runs and the full Linux suite passed under instrumentation.
  • Resolution: Fixture JSON reads now report method, pathname, status and content type, without response bodies, cookies or request payloads. No delay, retry or weakened preservation assertion was added.
  • Regression signal: pnpm test:orchestrator retains the real process-stop/restart and once-only upgrade PUT assertions; subsequent CI failures will identify the exact HTTP boundary.
  • Prevention rule: Preserve safe request context when parsing fixture responses so a local transport failure is distinguishable from a production reconciliation failure before choosing a fix.

2026-09-06 — Consume accepted responses before native restart tests #

  • Affected area: The upgrade process-restart case in tests/orchestrator.test.mjs.
  • Symptom signature: With Linux x64, Node 24.14.0 and CI=true, the first POST /__test__/provider/inspect after an accepted upgrade returned HTTP 500 with a non-JSON response. This happened before worker.stop(), after the preceding upgrade cases passed.
  • Root cause: The fixture checked only the upgrade response's 202 headers and left its JSON body unread before polling. Native phase probes showed that the failed inspection never entered the fixture Worker, while the provider continued serving other requests. Consuming the accepted body removed the failure; reversing only that change restored it. The precise internal Wrangler connection failure mechanism was not established.
  • Resolution: Consume and validate the accepted response's installation.status === "updating" before inspection. Preserve the held upload, actual process restart, stable-state assertions and exactly-two-total-Worker-PUT assertion; add no retry or delay.
  • Regression signal: CI=true pnpm test:orchestrator in Linux x64 Node 24.14.0: the original full suite failed, the response assertion passed all 17 tests, and reversing the assertion reproduced the same inspection failure. Run this CI-mode gate when changing the fixture HTTP lifecycle.
  • Prevention rule: Complete and validate HTTP response bodies before advancing a native lifecycle test. Do not mistake a headers-only acknowledgement for a completed fixture exchange, or attribute a pre-stop transport failure to restart readiness.

2026-09-06 — Hold observed native phases instead of racing UI polling #

  • Affected area: tests/installation-status.test.mjs and the native provider fixture in tests/fixtures/orchestrator-worker.ts.
  • Symptom signature: Linux CI timed out waiting for the active “Verifying installation” progress step after observing “Provisioning your Sandbox”. The real installation UI polls every 2 seconds.
  • Root cause: The fixture delayed each provider operation for only 1.8 seconds, so a valid native phase could start and finish between UI reads. Sequential assertions that every transient phase appears depended on polling alignment; a longer assertion timeout cannot recover a phase that already completed.
  • Resolution: Test-only latches hold the Containers create request and health result separately, keyed by installation resource name and phase. The browser observes each active phase, confirms matching native installation metadata with no installed release yet, then explicitly releases its provider operation. Each latch has a 45-second failure deadline, and finally releases both even when an assertion fails. The old timing delays are removed; production polling and Workflow transitions are unchanged.
  • Regression signal: CI=true pnpm test:installation-status exercises the actual built browser UI and native Workflow with deterministic provisioning/verifying observations, then requires Ready. The captured CI failure at the former verifying-step wait is preserved in the task handoff.
  • Prevention rule: When a browser test must observe an intermediate asynchronous phase, hold the corresponding test provider operation until that observation. Do not assume a fixed sleep exceeds every polling interval, scheduling delay or browser round trip.

2026-09-06 — Select one format from mixed Workers AI stream events #

  • Affected area: workers-ai-provider@4.0.0 streaming adapter and shell tool calls.
  • Symptom signature: A single echo Hello World request produced eight failed shell calls with interleaved JSON such as {"command": "{"command": "echoecho Hello Hello World"} World"}, and streamed prose repeated every token.
  • Root cause: Live Workers AI events contained both native top-level fields and their OpenAI-compatible equivalents. The provider processed both representations, emitting every text and tool-argument fragment twice.
  • Resolution: The pinned provider patch gives native response and tool_calls precedence within a mixed event, while retaining the OpenAI-compatible path when native fields are absent.
  • Regression signal: pnpm test:providers feeds mixed-format SSE events through the public Workers AI adapter and requires one text fragment and one valid tool input. A live local smoke call must execute one shell invocation with stdout Hello World and unduplicated final prose.
  • Prevention rule: Treat alternate wire representations within one provider event as mutually exclusive. Test the adapter with the exact mixed event shape returned by live inference.

2026-09-06 — Numeric zero is tool input, not a finalization sentinel #

  • Affected area: workers-ai-provider@4.0.0 streaming tool-call assembly and quote-heavy shell scripts.
  • Symptom signature: A Fibonacci command arrived as {} with an unterminated JSON error, or reached Bash truncated at .slice(. The remaining command was printed as pseudo shell(...) text.
  • Root cause: Workers AI emitted the 0 in .slice(0, ...) as a numeric argument fragment. The provider used !args to detect the end of a tool call, so numeric zero closed the call before the remaining JSON arrived.
  • Resolution: Only null, undefined, and an empty string finalize an argument stream. The shell schema and instructions also direct multiline JavaScript through a single-quoted heredoc and require structured retries.
  • Regression signal: pnpm test:providers sends a mixed-format stream with a numeric-zero fragment and requires the complete printf 0 input. The live Fibonacci request executes one heredoc command and returns all ten rows.
  • Prevention rule: Never use truthiness to classify streamed protocol values; valid argument fragments can be 0, false, or an empty-looking scalar.

2026-09-06 — Explicit URL reads need first-step tool selection #

  • Affected area: Think turn assembly and the read_url/browser_read tools.
  • Symptom signature: Flarebot promised to read a URL, then printed text such as [read_url(url="https://…")] without creating any tool activity or page evidence.
  • Root cause: The model had all application tools under automatic selection, so an explicit URL request could be completed as prose instead of a structured call. A turn-wide forced choice was also incorrect because it repeated the reader on every agentic step.
  • Resolution: Explicit URL-reading intent, including contextual questions such as “what's this telling me?”, now selects the appropriate reader through Think's native beforeStep hook on step zero only. Later steps can consume the result and answer. Web instructions reject pseudo-call syntax, and a short retry can recover the intended reader from the previous assistant response.
  • Regression signal: pnpm test:think covers direct reads, contextual link questions, rendered reads, pseudo-call retries, non-reading URL text, and continuation behavior. Live local requests for the reported Hacker News and Cloudflare documentation URLs each show exactly one successful Read webpage activity followed by sourced Markdown.
  • Prevention rule: When a request requires a tool, enforce it at the first model step. Do not use turn-wide tool choice for an agentic loop that must answer after receiving the result.

2026-09-06 — Unified catalog models retain their declared wire format #

  • Affected area: worker/model-provider.ts, Cloudflare unified AI catalog models
  • Symptom signature: Selecting thinkingmachines/inkling-256k succeeded in Settings, but sending an ordinary message returned a model request error or completed without response text.
  • Root cause: The model was added to the Workers AI allowlist and passed to the generic Workers AI adapter, whose stream mapper expects native/OpenAI-compatible events. Cloudflare exposes Inkling only through the Anthropic Messages request and response format, so its content events were not understood.
  • Resolution: Inkling now uses the official Anthropic AI SDK converter and parser while transport remains the native Cloudflare AI.run binding with the default AI Gateway and session affinity.
  • Regression signal: pnpm test:providers feeds an Anthropic Messages event stream through the configured Inkling model and requires its text delta.
  • Prevention rule: Before allowlisting a unified catalog slug, route it by the request format declared in Cloudflare's model catalog and test that exact streaming event shape through the production model factory.

2026-09-06 — Cross-isolate expirations need validation margin #

  • Affected area: Production owner-login challenge creation across the customer Worker and PersonalAgent Durable Object.
  • Symptom signature: /auth/login returned the generic installation-verification 403 even though all production variables, the secret, owner identity, bridge key, and Durable Object namespace were correct.
  • Root cause: The Worker issued expiresAt at the store's exact ten-minute maximum. The Durable Object validated the timestamp against its own request clock, so small cross-isolate clock skew could reject the challenge as too far in the future.
  • Resolution: Login challenges use a nine-minute lifetime while the store retains the ten-minute absolute validation ceiling. The public response remains generic and no request or credential data is logged.
  • Regression signal: The production login route creates a native PersonalAgent challenge and redirects to the configured control plane after a code update; the bridge integration gate retains expiry and replay validation.
  • Prevention rule: Do not issue distributed expiry values at the validator's exact upper boundary. Reserve explicit time for request transit and clock skew.

2026-09-06 — Third-party AI Gateway balance failures need an actionable boundary error #

  • Affected area: worker/model-provider.ts, Cloudflare unified AI catalog billing
  • Symptom signature: Retrying thinkingmachines/inkling-256k in the local conversation returned “The response could not be completed” even after its Anthropic Messages adapter was installed.
  • Root cause: A direct call through the same remote AI binding returned HTTP 402, code 2021: the account's default AI Gateway had insufficient credits. The model boundary collapsed 402 into the generic Workers AI failure.
  • Resolution: HTTP 402 is sanitized to an actionable insufficient-balance error. Actual inference remains unavailable until the account adds AI Gateway credits (or configures a supported BYOK route).
  • Regression signal: pnpm test:providers injects a 402 response through the configured Inkling model and requires the bounded AI Gateway balance error; the direct remote binding repro remains HTTP 402 until billing changes.
  • Prevention rule: Before debugging a third-party model's request or stream codec, probe its native Cloudflare binding status. Preserve actionable authentication, rate-limit, and payment categories while discarding provider response bodies.

2026-09-06 — Local AI configuration leaked into release artifacts #

  • Affected area: scripts/build-release.mjs, deployment fixtures, and the production artifact validator.
  • Symptom signature: CI rejected the public-fetch compatibility fixture with artifact_unavailable; installation tests could not accept the generated release.
  • Root cause: Adding ai.remote: true for local inference copied a development-only option into deployment.json. The installer correctly accepts only the AI binding name. Existing packaging tests checked that name but never loaded the complete release through the installer validator.
  • Resolution: Package only the AI binding name, retain the local remote-inference setting, and validate the actual packaged bytes through loadArtifact. Compatibility fixtures must derive their deployment configuration from the packaged artifact, not local Wrangler settings.
  • Regression signal: pnpm test:deployment includes a production artifact-validator test that failed on the original package and passes after rebuilding. The public-fetch fixture also rejects the original source-derived configuration and accepts the packaged one.
  • Prevention rule: Project development configuration into the explicit installation contract. Exercise the production artifact consumer against the exact packaged bytes; do not weaken its schema to accept local-only settings.

2026-09-06 — Optional providers need explicit provisioned enablement #

  • Affected area: control-plane/installation-workflow.ts, control-plane/deployment-api.ts, and OAuth capability coverage.
  • Symptom signature: OpenCode was selectable and its key could be saved, but the owner had never configured the required AI Gateway custom provider. The installation had nevertheless reached ready.
  • Root cause: The runtime assumed an enabled account-level opencode-go route, while install/upgrade provisioned only Workers and Containers. Gateway setup existed solely as a manual documentation prerequisite.
  • Resolution: Install/upgrade automatically reconcile only default. Explicit OpenCode enablement checks owner, grant and account membership, provisions the trusted route, and sends a signed receipt to persist runtime enablement. Key/model writes and inference cannot bypass that gate. Live rollout still requires publisher OAuth configuration and customer authorization; successful live inference with the user's key has not yet been verified.
  • Regression signal: pnpm test:deployment-network covers idempotent setup and conflicting URLs. pnpm test:orchestrator requires automatic default gateway setup without optional providers. pnpm test:bridge exercises two-site enablement, reconnect, lost replies, failed acknowledgment and durable enablement; pnpm test:settings covers disabled model/key controls.
  • Prevention rule: Separate always-on infrastructure from opt-in provider setup. A stored key is not proof that a provider is enabled. Validate credential destinations and require a verified provisioning acknowledgment before exposing inference.

2026-09-06 — Model failure exports discarded the diagnostic status #

  • Affected area: worker/model-provider.ts, worker/conversation.ts, and the diagnostic schema.
  • Symptom signature: A dev.4 export recorded an OpenCode Go gpt-5.6-luna attempt failing after 582 ms, but contained only status: error; the conversation displayed a generic failed-response banner.
  • Root cause: The model boundary recorded terminal status without the provider HTTP status or failure stage, then sanitized the error. The original OpenCode failure remains unclassified; this finding concerns the lost diagnostic evidence, not its upstream cause.
  • Resolution: Local diagnostics retain only integer HTTP error statuses (400–599, otherwise null) and an allowlisted request/stream failure stage. No messages, bodies, headers, credentials, or prompts are retained. Production deployment and a fresh failing attempt are still required to diagnose the original failure.
  • Regression signal: node --test --test-concurrency=1 tests/model-provider.test.mjs tests/diagnostics.test.mjs tests/diagnostics-native.test.mjs covers thrown and streamed failures, rejects arbitrary diagnostic fields, and verifies a native exported 401 with the request stage.
  • Prevention rule: Preserve bounded protocol evidence before sanitization. A generic error flag cannot distinguish authentication, billing, routing, or malformed-response failures; never infer one from latency alone.

2026-09-06 — Reachable local control plane is not configured provider setup #

  • Affected area: Local control-plane configuration, control-plane/config.ts, control-plane/installation-metadata.ts, and OpenCode Settings enablement.
  • Symptom signature: Local /connect rendered successfully, but authorization showed "The Flarebot publisher needs to verify deployment permissions for this release" and the runtime's OpenCode key input was disabled.
  • Root cause: The local control plane was started with a placeholder OAuth client and no capability manifest. The local runtime also lacked a bridge key and provider receipt. The installation registry accepts deployed workers.dev origins, not localhost; merely starting both servers cannot establish a full local installation workflow.
  • Resolution: Registered a separate development OAuth client, verified its real grant/UserInfo/account access, and restarted the control plane with its validated configuration. For this debugging session only, verified the existing enabled gateway route and issued a localhost-bound receipt using an independent development signing key. Production gates and registry records were not changed. Full localhost install/upgrade support remains separate work.
  • Regression signal: Live local POST /auth/start changed from 503 to 303; /auth/provider-enabled accepted the development receipt with 200. A Playwright check selected OpenCode Go, typed into the API key field, and asserted that Save key became enabled without saving a credential or sending inference.
  • Prevention rule: Verify authorization and provider enablement, not just page availability, before claiming a local setup is usable. Keep development keys separate, verify the destination before issuing a receipt, and never represent local bootstrap as a completed deployed installation.

2026-09-06 — Missing local Sandbox image is not a model failure #

  • Affected area: Long-running local Wrangler runtime and Docker Sandbox execution.
  • Symptom signature: OpenRouter streamed text and emitted a shell call successfully, but the tool failed after about 12 seconds. Wrangler logged No such image available named cloudflare-dev/sandbox:9c19c745 and Container failed to start.
  • Root cause: The running dev runtime referenced a generated Docker image tag that no longer existed. docker image inspect independently confirmed its absence; the cause of removal was not established.
  • Resolution: Restarted local Wrangler to prepare a fresh Sandbox image. A new OpenRouter turn then executed the harmless printf command successfully, with the expected marker in the actual tool output. No provider or production configuration changed.
  • Regression signal: Live diagnostics changed from shell failed to succeeded (one attempt, about 1.3 seconds). Both the model's tool-selection and follow-up streaming calls completed. The temporary API key was removed and the previous model restored afterward.
  • Prevention rule: Verify actual tool status and output rather than an assistant's mention of expected output. For local container startup errors, check the exact referenced Docker image before changing model routing, tool schemas, or timeouts.

2026-09-06 — Publisher release bump preserved an obsolete permission attestation #

  • Affected area: Publisher deployment configuration, control-plane/config.ts, and release verification.
  • Symptom signature: After deploying dev.6, /connect returned HTTP 200 but /api/connection returned oauth_capability_unavailable: "The Flarebot publisher needs to verify deployment permissions for this release."
  • Root cause: keep_vars preserved FLAREBOT_CONTROL_PLANE.oauthCapabilities.artifactVersion as dev.5. The configuration gate correctly requires it to match the deployed package version. Static-page and bundle checks missed that gate; OAuth fixtures generated matching versions automatically.
  • Resolution: Reviewed the unchanged required capability contract and updated only the production attestation's artifact version to dev.6. No client registration, OAuth scopes, secrets, or personal Worker changes were needed.
  • Regression signal: The actual configuration function rejects the saved dev.5 attestation and accepts the same configuration with dev.6. pnpm test:oauth covers wrong-release rejection; release smoke checks must exercise live /api/connection and /auth/start, not just /connect.
  • Prevention rule: Review and update the publisher permission attestation on every release bump. Preserve unrelated bindings, but do not mistake a preserved version-bound attestation for a valid new-release configuration. Require unauthenticated connection HTTP 401 not_connected and authorization-start HTTP 303 before declaring onboarding ready.

2026-09-06 — Local AI smoke tests need the remote-binding dev API #

  • Affected area: tests/web-search-live.test.mjs, Wrangler local AI bindings.
  • Symptom signature: The live search smoke returned search_unavailable immediately under unstable_dev({ local: true }), despite ai.remote: true in its config.
  • Root cause: The older dev API did not establish the remote binding connection required by the native AI wrapper. Local fixture credentials also deliberately cannot access Cloudflare.
  • Resolution: Run the opt-in paid smoke separately from tests/fixtures/config.mjs, using unstable_startWorker with a remote AI binding and without dev.remote: false.
  • Regression signal: FLAREBOT_WEB_LIVE_SMOKE=1 node --test tests/web-search-live.test.mjs changed from an immediate failure to three real provider sources in about 12 seconds, without changing the search request.
  • Prevention rule: Verify that a live inference harness establishes remote bindings before diagnosing model access or request shape. Keep paid smoke authentication separate from credentials-free fixtures.

2026-09-07 — Reasoning signatures are protocol state, not diagnostics #

  • Affected area: worker/model-provider.ts, Anthropic-compatible reasoning and tool follow-ups.
  • Symptom signature: Enabling adaptive reasoning while stripping all stream providerMetadata removes the signature carried by an empty reasoning delta. The next SDK-generated tool-follow-up request loses the signed thinking block; redacted thinking is likewise lost.
  • Root cause: The privacy boundary treated every provider metadata field as optional diagnostics, but the Anthropic adapter uses anthropic.signature and anthropic.redactedData to reconstruct required conversation blocks.
  • Resolution: Preserve only those string fields on reasoning chunks and generated reasoning parts. Continue removing other provider metadata, raw events, request/response envelopes, and unsafe errors.
  • Regression signal: pnpm test:providers exercises a native Anthropic SDK two-step tool round-trip with empty signed thinking and redacted thinking, and separately checks that unrelated metadata is stripped.
  • Prevention rule: When enabling reasoning, verify the actual follow-up request—not just the initial effort payload. Preserve required opaque protocol state and empty signature-bearing deltas without treating them as user-visible diagnostics.

2026-09-07 — Queued submission metadata is not active turn metadata #

  • Affected area: worker/conversation.ts, Think 0.17 scheduled model selection.
  • Symptom signature: A scheduled task in a chat with High effort used High even after the installation default became Low; the submission inspection correctly contained Low.
  • Root cause: submitMessages(..., { metadata }) stores queue-ledger metadata but does not stamp the user message's reserved turnMetadata. activeTurnMetadata therefore did not identify the scheduled turn, and beforeTurn fell back to the last interactive request body.
  • Resolution: Also carry the scheduled model snapshot in application-owned metadata on the submitted user message and resolve that snapshot ahead of the interactive body. Keep queue metadata for submission inspection and task lifecycle handling.
  • Regression signal: FLAREBOT_EXECUTION_CASE='scheduled turns ignore' node --test --test-reporter=tap tests/execution.test.mjs failed with High versus Low, then passed with the scheduled tool follow-up using Low, the chat override remaining High, and a subsequent interactive message using High.
  • Prevention rule: Test each native submission path's actual hook-visible data. Do not assume queue inspection metadata is automatically available through per-turn APIs.

2026-09-07 — Catalog removal broke persisted model settings and conversation startup #

  • Affected area: shared/model-providers.ts, PersonalAgent.getModelSettings, and ConversationSession.connect.
  • Symptom signature: Settings cannot load model settings; conversation startup repeatedly reports connection loss after successfully opening its socket.
  • Root cause: dev.6 offered OpenRouter openai/gpt-5-mini. Removing it from the dev.7 catalog made the strict parser reject existing saved settings. Conversation startup awaits the same settings read and classifies its RPC failure as a reconnectable connection failure. Seeding the dev.6 selection in native SQLite reproduced the loop; the reported live diagnostics did not expose the stored selection, so the live cause remains unconfirmed.
  • Resolution: Retain the previously supported model and its provider-default effort behavior. Do not rewrite the owner's model, credentials, or conversations.
  • Regression signal: FLAREBOT_CHAT_CASE='previous release model' node --test --test-reporter=tap tests/chat-ui.test.mjs seeds the old selection directly in native storage and checks conversation readiness, unchanged settings, and the real Settings UI.
  • Prevention rule: A selectable-model catalog is also a persisted-data contract. Preserve previously valid selections or provide an explicit migration before removing an entry; exercise reads through upgraded storage, not only fresh installations.

2026-09-07 — Completed search response contained an unfinished search item #

  • Affected area: worker/web-search.ts, native AI Gateway Responses validation.
  • Symptom signature: web_search returns invalid_search_response for ordinary monitor queries despite HTTP 200.
  • Root cause: One authorized replay of 1440p 120Hz OLED monitor USB-C Mac returned overall status: completed, a completed search with 22 source entries, a second search still marked searching with three source entries, and a completed message with four citations. The request specified max_tool_calls: 1, but two search items were returned. Flarebot rejects the entire response when any search item is not completed. The provider's reason for the inconsistent item state is unknown.
  • Resolution: Select only completed search items for source-list extraction while retaining completed-message citations and overall completion validation. Require at least one completed search and reject an empty result when another search remains unfinished. Do not relabel unfinished calls as completed or retry paid requests automatically.
  • Regression signal: A single live replay and an offline Miniflare replay reproduced the rejection. pnpm test:web exercises the mixed response through native tool execution and history, requires the completed source and summary, excludes the unfinished source, and rejects unfinished-only and mixed-empty responses. The mixed-response assertion failed before the fix.
  • Prevention rule: Validate response-level and item-level completion separately. Retain bounded failure classifications so mixed completion states can be distinguished from missing sources or invalid JSON without exporting queries or response content.

2026-09-07 — Custom Domain IDs differ from zone IDs #

  • Affected area: control-plane/domain-api.ts, control-plane/domain-metadata.ts, control-plane/installation-registry.ts.
  • Symptom signature: Cloudflare attaches the hostname and serves the app, but domain setup reports failure and /auth/login returns 403 with "Owner authentication or installation verification failed."
  • Root cause: The live Workers Domains API returned a 40-character hexadecimal domain ID. Both the API verifier and persisted domain schema assumed 32 characters, rejecting the attached mapping before runtime authorization and HTTPS verification. Replaying that identifier shape through the native provisioning fixture reproduces the rejection; the authenticated live workflow record was not available for inspection.
  • Resolution: Use a dedicated domain ID schema accepting the existing 32-character and observed 40-character formats. Keep account/zone IDs and ownership checks strict. Deployment and an authenticated retry are needed to reconcile the existing attachment; do not delete or reattach it.
  • Regression signal: node --test tests/domain-provisioning.test.mjs tests/domain-workflow.test.mjs exercises 40-character IDs through API reconciliation, SQLite persistence, activation and removal. The reconciliation test failed before the fix.
  • Prevention rule: Do not reuse account/zone identifier formats for other provider resources. Exercise provider-observed identifiers through both response validation and durable storage, including recovery after an accepted write.

2026-09-07 — Settings anchor jumps raced responsive navigation measurement #

  • Affected area: src/components/settings-navigation.tsx.
  • Symptom signature: Keyboard navigation immediately after resizing to 1024px put the Memory heading under the sticky navigation. At 390px the jump could leave the active-section indicator on the preceding section.
  • Root cause: The native anchor jump used a scroll margin last measured for the previous layout. Deferred resize measurement and responsive layout changes then changed the offset without realigning the target.
  • Resolution: Measure on link activation and realign the target on the next animation frame with the current navigation height, retaining native hash navigation and focus.
  • Regression signal: pnpm test:settings failed its heading-visibility assertion before the fix and passes responsive keyboard navigation, manual scrolling and active-section checks afterward.
  • Prevention rule: When native scrolling depends on dynamically measured sticky content, synchronize the measurement with navigation and test activation across breakpoint changes, not only settled layouts.

2026-09-08 — Resource navigation tests left later chat cases on Settings #

  • Affected area: tests/chat-ui.test.mjs, PR #45 browser CI.
  • Symptom signature: The memory banner test passes, then the reload/offline and pending-stream tests time out finding the Message textbox.
  • Root cause: The new memory-link case ends on Settings. The next two cases share its browser page and send messages without opening a conversation. Running the resource case alone missed this dependency.
  • Resolution: Explicitly open the fixture conversation at the start of each affected chat case.
  • Regression signal: The full CI=true pnpm test:chat-ui run reproduces the missing-composer timeout before the fix; run the complete sequence to verify navigation isolation.
  • Prevention rule: Each browser case must establish its starting route. After adding a navigation test, run the full containing suite as well as focused cases.

2026-09-07 — Default Wrangler target was not the live installation #

  • Affected area: Manual deployment using wrangler.jsonc.
  • Symptom signature: A successful wrangler deploy reported flarebot.nbedd2.workers.dev, whose homepage returned HTTP 503 with invalid configuration, while the owner's existing app still worked.
  • Root cause: The default flarebot Worker was mistaken for the installed customer Worker. Both its previous and newly deployed versions lacked installation configuration bindings. The custom domain actually mapped to a separate flarebot-<installationId> Worker.
  • Resolution: Confirmed https://bot.nathanbeddoe.com/ and the publisher's /connect returned HTTP 200. Neither the installed customer Worker nor publisher was updated by this deployment; the intended release target still needs to be resolved.
  • Regression signal: Cloudflare domain mapping identifies the installed Worker; curl https://flarebot.nbedd2.workers.dev/ still returns the configuration error, while curl https://bot.nathanbeddoe.com/ returns HTTP 200.
  • Prevention rule: Resolve the live domain, Worker identity, and release workflow before deploying. A default Wrangler target and successful upload do not establish that the user's installation was updated. Compare prior bindings before attributing a post-deploy error to changed configuration.

2026-09-08 — Publisher origin migration left customer bridge pins behind #

  • Affected area: Publisher publicOrigin, customer FLAREBOT_INSTALLATION.controlPlaneOrigin, and control-plane/bridge.ts / worker/bridge.ts.
  • Symptom signature: After moving the publisher to control.flarebot.app, an existing installation's login redirects to the old publisher hostname and receives HTTP 403 bridge_denied.
  • Root cause: The publisher's exact-origin gate now requires the new hostname, while the deployed customer still pins the old origin for login redirects, code exchange, and assertion issuer verification. Keeping the old workers.dev endpoint enabled does not make it an accepted bridge origin.
  • Resolution: Migrated the existing customer's pinned origin to https://control.flarebot.app with authorization, preserving its other installation fields, bindings and runtime metadata. No browser-only redirect or weakened issuer validation was added.
  • Regression signal: Live curl requests to /auth/login on both customer hostnames originally returned 303 to the old publisher, followed by 403 bridge_denied. After migration both return 303 to the new bridge and then 303 into OAuth with the new callback. Full authenticated callback verification remains outstanding.
  • Prevention rule: Treat a publisher origin change as a migration of every installed customer's trust configuration, not just DNS and OAuth. Verify customer login, server-side exchange, and issuer checks as well as publisher onboarding before declaring the migration complete.

2026-09-08 — Worker settings patch dropped container runtime metadata #

  • Affected area: Cloudflare Workers script settings PATCH for a container-backed customer Worker.
  • Symptom signature: A successful binding-only settings update retained bindings and exports but removed resources.script_runtime.containers from the active version.
  • Root cause: The settings PATCH schema does not support containers; supplying the previous container mapping there did not preserve it. Adding an export's container reference was rejected because that endpoint did not declare the referenced container.
  • Resolution: Re-uploaded the existing deployed text bundle through script PUT with the original exports and container mapping, inherited bindings and retained assets. No local rebuild or container application change was performed.
  • Regression signal: Compared original and final active-version resources after normalizing binding order and the intended installation-origin change; all other version resources matched, including the Sandbox container mapping. Both live login redirect checks passed afterward.
  • Prevention rule: For container-backed Workers, verify active-version container metadata after configuration changes. Use a metadata-preserving script upload when settings PATCH cannot represent the original runtime configuration; do not equate HTTP 200 with preservation.

2026-09-08 — Inline model search collapsed the mobile composer label #

  • Affected area: Chat model selector and composer browser coverage.
  • Symptom signature: The mobile composer showed only a few letters of the selected model beside clear and caret icons, even though the control stayed within the composer bounds.
  • Root cause: An inline combobox input competed for the compact footer width with its clear/caret controls and the effort/send controls. The previous responsive checks verified bounding boxes but not readable label width or the open picker with reduced keyboard space.
  • Resolution: Use a compact model button and a separate Kumo dialog with searchable, scrollable options and pricing badges. Bound the dialog to the visual viewport. Reuse the command palette's native filtering and explicitly synchronize its active descendant after filtered rows change, as in the existing command palette.
  • Regression signal: The model/effort browser case checks untruncated default text at 320px and 390px, a 44px model tap target, dark-mode screenshots, a 390×420 viewport, empty results, Escape dismissal, and keyboard selection with a valid active descendant.
  • Prevention rule: Responsive interaction checks must verify readable text and the open control under keyboard constraints, not just whether closed control rectangles fit.

2026-09-09 — A pre-aborted Effect boundary still started a native request #

  • Affected area: Customer tool execution and request/action Effect boundaries, including Skill package review.
  • Symptom signature: The native web regression observed an extra AI request when its caller signal was already aborted. The tool returned cancellation, but the request had started.
  • Root cause: In Effect 4.0.0-rc.112, the Promise runner initially evaluates the fiber before checking an already-aborted external signal. Supplying runPromise(program, { signal }) alone therefore does not prevent initial native work.
  • Resolution: Check the signal before invoking the Effect runner at externally cancellable boundaries, then pass it through for subsequent interruption. Native resource ownership still handles operations that have already started.
  • Regression signal: The focused Effect test counts binding calls outside the mock and requires zero for a pre-aborted invocation. The native web suite also checks cancellation before a search request. Native installer tests construct pre-aborted upload and URL Requests at the production installer boundary and require zero downloads/writes plus successful confirmation of the previous review.
  • Prevention rule: Assert observable call counts, not an assertion thrown inside a mocked provider operation: a safe error boundary can catch that assertion and make a broken test appear to pass.

2026-09-09 — Query wrappers missed Octane client hook slots #

  • Affected area: Customer Query binding hooks with Octane 0.2.2 and Vite plugin 0.1.52.
  • Symptom signature: Typecheck and public SSR pass, but hydration reaches the router error screen with Cannot read properties of undefined (reading 'getOptimisticResult').
  • Root cause: The generated client left useQuery(...) calls inside plain .ts resource wrappers without a slot argument. Those files imported the Query binding and another local hook, without a direct Octane hook import. The binding therefore used an undefined slot for its internal observer state.
  • Resolution: Author resource hooks as .tsx so this compiler slots their nested hook calls. The binding and Octane/router versions stay pinned; no package patch or manual hook wrapper was introduced.
  • Regression signal: The packaged Settings browser suite failed during its initial unauthenticated visit before the change. It now hydrates, shares query observers and passes native Settings/cache lifecycle cases.
  • Prevention rule: Inspect generated client hook arguments when adopting a binding; typechecking and SSR alone do not establish browser compatibility. Keep this packaged browser regression when upgrading the compiler or binding.

2026-09-09 — Optional positional hook arguments consumed Octane’s slot #

  • Affected area: Publisher resource hooks with the pinned Octane compiler.
  • Symptom signature: Provider/domain setup becomes blank after verified identity loads, with Expected enabled to be a boolean or a callback that returns a boolean; the update page using the same query still works.
  • Root cause: The compiler appends a slot argument to custom hook calls. Calling a hook with omitted optional positional arguments let that slot occupy the enabled parameter. The update page supplied every argument and avoided the bug.
  • Resolution: Use one required options object for configurable custom hooks, with defaults inside the destructured object. Extra compiler arguments cannot fill an omitted field.
  • Regression signal: Native provider setup and the domain browser fixture exercise the short options form, while update-on-visit exercises explicit polling options.
  • Prevention rule: Avoid optional positional parameters in compiled custom hooks; test every supported calling form in the built browser runtime.

2026-09-10 — Identity cleanup detached the native logout form #

  • Affected area: Publisher Query owner changes and the disconnect form.
  • Symptom signature: Disconnect showed a signed-out screen in the original tab, but another tab still loaded the previous owner's accounts and never offered Connect Cloudflare.
  • Root cause: Clearing the Query session identity inside onSubmit changed the owner epoch and remounted the form before its default browser submission. The authenticated cookie remained because the native disconnect POST never completed.
  • Resolution: Cancel local commands and clear saved intents without changing the mounted form's owner key. The native disconnect response clears the cookie and navigates away, retiring the old cache.
  • Regression signal: The installation browser suite requires the real disconnect POST to return 303, the authenticated cookie to disappear, and a second owner to see an empty installation list with no inherited private state.
  • Prevention rule: Preserve a native form through its default submission. A locally signed-out view is not evidence that server-side logout occurred; assert the native request and cookie transition.

2026-09-09 — Effect adapters changed cleanup and error boundaries #

  • Symptom signature: Returning a shell iterator while reservation was pending left the late lease unclosed. Completed browser commands retained abort listeners. Provider stream setup defects escaped sanitization and left diagnostic attempts unfinished. An unreadable credential reached turn preparation as “Personal runtime unavailable.”
  • Root cause: An interruptible Promise adapter discarded the reservation before its scope owned the handle; Effect.callback cleanup runs on interruption, not normal settlement; provider guards covered only Promise rejection; generic RPC adapters discarded declared application errors.
  • Resolution: Complete the shell lease handoff before scope interruption, reuse the browser deadline race, guard complete provider callbacks and release failed readers, and carry declared internal RPC failures as plain codes. Browser callables retain their existing payloads and messages. Unexpected RPC failures remain generic at the caller.
  • Regression signal: pnpm test:customer-runtime-effect covers return during native reservation, listener removal on every completion path, native browser races, and actual DO RPC with unreadable credentials and conflicting memory versions. pnpm test:providers covers locked streams and synchronous normalization defects as well as native provider failures.
  • Prevention rule: Test consumer return independently from abort signals. Verify callback cleanup on success as well as interruption, encompass synchronous setup in external error boundaries, and test declared failures across real RPC serialization.

2026-09-10 — Cached Query errors repeatedly retired healthy owner sockets #

  • Symptom signature: A failed background conversation-list read followed by offline/online events left the shell repeatedly reconnecting despite successful native authentication.
  • Root cause: Query retains a background error while a replacement fetch is running. The shell treated that cached error as a new connection failure and retired the socket before recovery could finish.
  • Resolution: Only a successful settled list confirms initial readiness; read errors remain in the query UI and never drive native socket retirement.
  • Regression signal: The packaged shell test failed waiting for Connected before the fix after one rejected native list RPC and offline/online events. It now requires reconnection, cleared read errors and restored conversation navigation.
  • Prevention rule: Keep query fetch/error state separate from native connection failure handling. Test retained errors during reconnect, not just clean reconnects or isolated read retries.

2026-09-10 — Failed pagination removed the last successful installation page #

  • Symptom signature: A next-page HTTP 503 erased the installation list and pagination controls despite a successfully loaded first page.
  • Root cause: The controller committed the requested cursor before loading it and relied on Query placeholder data. The placeholder disappeared when that new query failed.
  • Resolution: Fetch the requested page into Query before committing the displayed cursor. Preserve the current page on failure and let an explicit retry request the same next cursor.
  • Regression signal: The native installation browser fixture failed its retained-row assertion before the fix. It now checks all 50 rows and navigation controls after a failed next-page GET, then successfully retries pagination.
  • Prevention rule: Treat page navigation as a commit after successful loading. Placeholder data alone does not preserve a previous page through an error.

2026-09-09 — Effect shell iterable lost Think preliminary output #

  • Affected area: worker/shell-tool.ts, Think 0.17.0 tool execution, and tests/shell.test.mjs.
  • Symptom signature: The native shell test times out waiting for preliminary stdout. The command actually succeeds, returns its complete stdout/stderr, and closes the container, but the client receives only the final tool output.
  • Root cause: The migrated execute is an ordinary function returning Stream.toAsyncIterable. Think selects its streaming wrapper only for an AsyncGeneratorFunction; it consumes other returned async iterables internally and returns their last value.
  • Resolution: Declare execute as an async function* delegating to the Effect iterable. This restores preliminary output while retaining Effect-owned execution and cleanup.
  • Regression signal: CI=true node --test tests/shell.test.mjs failed at the first preliminary-output wait before the fix. The complete native shell suite now passes, including streaming, cancellation, limits, deletion and restart cleanup; the nine focused customer Effect tests also pass.
  • Prevention rule: Preserve the native tool execution function shape when wrapping Effect streams. Verify preliminary output through Think and its real chat transport, not only direct iteration or final command success.

2026-09-10 — Attachment cleanup must not wake a child during parent startup #

  • Affected area: Conversation deletion recovery in PersonalAgent.onStart.
  • Symptom signature: Repeated native blockConcurrencyWhile() timeouts after restarting with an unfinished conversation deletion.
  • Root cause: Attachment cleanup called subAgent() from parent startup recovery. The child's initialization could call back into the parent while its startup gate was closed.
  • Resolution: Delete the native facet, then remove its attachment objects directly from R2 using the workspace's conversation prefix. Retrying cleanup does not recreate the child.
  • Regression signal: pnpm test:think exercises failed deletion followed by a full Worker restart; pnpm test:attachments checks repeatable R2 cleanup and isolation from other conversation objects.

2026-09-10 — Attachment transport tests did not establish actual reading #

  • Affected area: Image/PDF turn preparation and model capability selection.
  • Symptom signature: “What's this?” with an image produced a description of tool definitions; “The attached image?” produced an offer to read and a printed pseudo call.
  • Root cause: Attachment tools remained optional. The previous fixture explicitly selected the reader via a test-only resource command, proving byte transport but not the ordinary user path. Capability checks also restricted media by provider rather than the configured model's input modalities.
  • Resolution: Require the attachment reader on the first step of new uploads and explicit attachment follow-ups. Preserve automatic selection afterward. Record capabilities for every catalog model and carry OpenRouter image/PDF results through its supported user-message format.
  • Regression signal: The native browser test uses both reported phrases and requires first-step selection plus delivered image bytes. The opt-in live attachment test makes Scout and Qwen read a shape/color and printed label present only in the image. Provider tests inspect image/PDF request bodies on streaming and generation paths.

2026-09-10 — First attachment upgrade rejected its own new R2 binding #

  • Affected area: control-plane/deployment.ts, worker-deployment.ts, and start-installation.ts.
  • Symptom signature: dev.25 to dev.26 stops at deploying with resource_conflict, although the live Worker already serves dev.26 with the expected ATTACHMENTS bucket. Retry fails too; prior and current resources match except for that new binding.
  • Root cause: Post-upload verification and adoption compared the entire new binding fingerprint to the persisted pre-upgrade fingerprint. Adding ATTACHMENTS necessarily changed it. Tests bootstrapped the source with the new binding already present.
  • Resolution: Post-upload/recovery may also match the old fingerprint after excluding exactly the release-required, installation-owned ATTACHMENTS binding. Pre-upload checks remain exact; existing binding changes, metadata drift, and arbitrary R2 bindings are not exempted. Existing durable baselines need no migration.
  • Regression signal: pnpm test:orchestrator reproduces the failure by removing ATTACHMENTS from the source before a lost-response upgrade. node --test tests/deployment-effect.test.mjs checks the narrow addition and rejects binding replacement/removal, wrong bucket/type/jurisdiction, and unrelated metadata or binding changes.
  • Prevention rule: Upgrade fixtures must represent the previous release's resource set. Verify expected additive resources separately from inherited state, including recovery against already-persisted baselines.

2026-09-10 — MCP configuration acknowledgement waited for remote discovery #

  • Symptom signature: A valid add times out at the native browser RPC's ten-second limit, completes later, and blocks another server. After restart, the native SDK is ready while settings remains disconnected.
  • Root cause: The configuration callable awaited discovery under an installation-wide semaphore, and only explicit commands projected native results.
  • Resolution: Acknowledge persisted pending intent promptly; own Effect work with Agent waitUntil, serialize by server, and project each native restoration independently. An SDK-wide restoration wait inside per-server locks recreates cross-server blocking; native state events must complete each restored projection without that barrier. Query reloads the whole catalog after mutation instead of fabricating a partial initial list.
  • Regression signal: Native gated-discovery tests require prompt acknowledgement, independent auth progress, disable safety and full-restart readiness. The packaged Query test holds the initial list while adding and requires both old and new rows.

2026-09-10 — MCP OAuth callback redemption does not establish transport #

  • Symptom signature: Native callback returns authSuccess after valid PKCE redemption, but immediate discovery fails and settings reports an error.
  • Root cause: Agents handleCallbackRequest redeems authorization; its normal HTTP lifecycle separately establishes the transport afterward. The application callback adapter initially omitted that step.
  • Resolution: Use the shared native connect/discover path after successful callback redemption, under the same per-server Effect serialization and revision checks.
  • Regression signal: The native OAuth test restarts between authorization and callback, then requires authenticated capability discovery, refresh, reauthorization and credential removal.

2026-09-10 — OAuth return notice disappeared during owner reconnection #

  • Symptom signature: Authorization form posts and returns to Settings, but the callback result is never visible after the owner connection becomes ready.
  • Root cause: The component consumed the callback parameter before connection setup finished; its connection-reset effect then cleared the same notice state.
  • Resolution: Consume the fixed callback result after connection readiness and retain authorization feedback independently of mutation notices.
  • Regression signal: The packaged browser test submits the real form to a test redirect, requires the callback notice after reconnect, and confirms the result parameter is removed from the URL.

2026-09-10 — Restored OAuth refresh retained write authority after removal #

  • Symptom signature: Disable/remove clears OAuth records, then a held native restoration refresh writes a new encrypted token after cleanup. Reauthorization on a different approved origin also requests a redirect absent from the retained client registration.
  • Root cause: Native restoration runs outside application command locks. Its provider retained write/delete authority; callback URI changes also reused client metadata registered for the old URI.
  • Resolution: Give each native provider a revocable lease. Retire leases synchronously when disable/removal is accepted and when replacing a native connection. Check current authority at the final synchronous private-KV commit and before deletes. A callback change clears the superseded registration before SDK registration runs again.
  • Regression signal: Native tests hold restored refresh across disable, removal and immediate re-enable, then require unchanged current credential records after release. Reauthorization in both origin directions requires exact agreement between registered and requested redirect URIs.

2026-09-10 — Workers rejects fetch redirect error mode #

  • Symptom signature: MCP secret-header setup fails before sending a request; native negotiation reports an invalid redirect mode.
  • Root cause: Workers fetch supports follow/manual, but not the browser redirect error mode.
  • Resolution: Use manual redirects and reject 3xx responses explicitly before the SDK can follow them.
  • Regression signal: The native header test establishes a connection and then requires zero foreign-origin calls after a credential-bearing 307 response.

2026-09-10 — MCP protocol errors exposed credentials through native diagnostics #

  • Symptom signature: Header-auth Settings shows a safe connection error, but agents:mcp diagnostics contain the configured secret echoed by an HTTP-200 JSON-RPC error.
  • Root cause: Fetch scrubbed HTTP errors while the observability receiver forwarded native protocol error text, URLs and authorization metadata unchanged.
  • Resolution: Project native MCP events through an explicit safe field allowlist before genericObservability publication; redact URLs/auth data, replace errors with fixed messages, and suppress unknown MCP event shapes.
  • Regression signal: The native header fixture subscribes to the actual agents:mcp channel through the production receiver and rejects both credentials after HTTP errors and HTTP-200 protocol errors.

2026-09-10 — Execution timeout left native fixture resources alive #

  • Affected area: tests/fixtures/execution-suite.mjs, its execution-lifetime.mjs owner, and the execution test runner.
  • Symptom signature: CI reports the cron suite's six-minute timeout, then Node and workerd remain alive until workflow cancellation.
  • Root cause: Native clients and Worker disposal were owned only by the async test body's finally; a suspended readiness or HTTP await prevents that body from unwinding when Node cancels the test. In-memory cron polling also kept referenced sleep timers alive after cancellation, without reaching any native RPC/HTTP abort guard. The original CI await remains unidentified.
  • Resolution: Register bounded, idempotent resource cleanup before the first await, close clients and the events server independently of the body, dispose late-started Workers, and prevent reconnecting after cancellation. HTTP and every fixture sleep follow the parent signal, and polling checks cancellation before each read. Failure-only phase diagnostics and TAP preserve the next timeout's location.
  • Regression signal: node --test tests/execution-cleanup.test.mjs uses explicit fixture hooks to suspend real native client readiness and an HTTP response under an eight-second parent timeout. The original readiness probe required external termination at 17 seconds; the actual unattended cron cancellation probe required external termination at 18 seconds. Corrected probes exit nonzero with the timeout and phase diagnostic after roughly 8.5 seconds.
  • Prevention rule: Test parent timeout independently of assertion failure. Register cleanup before native readiness, and prove the process exits without an external kill; include in-memory polls and referenced timers as well as native handles. A test timeout alone does not release resources held by a suspended async body.

2026-09-10 — Execution fixture awaited a superseded native readiness promise #

  • Affected area: tests/fixtures/execution-suite.mjs, ExecutionLifetime.waitForReady.
  • Symptom signature: Execution CI times out before scenario entry at native client readiness; the cleanup probe never reaches its intended suspension, although its process exits on timeout.
  • Root cause: Agents replaces client.ready on socket close. A native disconnect before identity delivery leaves the fixture awaiting the old unresolved promise, even after the SDK reconnects and receives identity. A controlled native identity-drop probe reproduces this exact hang; the initial transport trigger in the reported CI run remains unconfirmed.
  • Resolution: Register open/close listeners before observing readiness, follow the current native promise, and remove listeners on resolution or parent cancellation. Keep SDK reconnect/backoff unchanged. Failure-only diagnostics record route kind, connection counters and readiness state without credentials or payloads.
  • Regression signal: The native reconnect case in tests/execution-cleanup.test.mjs drops only the first identity delivery, then requires native readiness and ordinary process completion. Reinstating the original await makes that case time out; existing readiness, HTTP and cron-poll cancellation probes still verify independent cleanup.
  • Prevention rule: Treat a replaceable readiness promise as belonging to one transport generation. Follow native lifecycle events instead of retaining its first value across reconnects.

2026-09-10 — Cleanup probes spent their cancellation deadline on startup #

  • Affected area: tests/execution-cleanup.test.mjs, tests/fixtures/execution-cleanup-probe.mjs, and the execution fixture lifecycle.
  • Symptom signature: CI's readiness probe times out before PROBE_NATIVE_READY; its reconnect probe reaches readiness but then times out during unrelated startup RPC checks. Both processes exit normally, while the HTTP and cron-poll cleanup probes pass.
  • Root cause: The eight-second test timeout included Worker startup and identity negotiation. Selecting a nonexistent scenario as “startup only” skipped named scenarios but still created conversations and ran common RPC checks. A controlled nine-second setup delay reproduces the missing readiness marker; the underlying CI connection error is not independently reproduced.
  • Resolution: Keep resource cleanup registered before setup, give setup a separate bounded allowance, and start the native eight-second child-test cancellation deadline only after readiness. Follow that child test's signal and cleanup hook. A real startup-only path returns before common scenario setup. The process watchdog has separate startup and post-readiness deadlines.
  • Regression signal: node --test tests/execution-cleanup.test.mjs includes setup deliberately slower than the lifecycle deadline, then requires the injected readiness hold, native test timeout, and ordinary process exit. All four probes pass; reconnect requires identity loss/recovery and no unrelated RPC errors.
  • Prevention rule: A cancellation probe must reach its intended suspension before its measured deadline begins. Register cleanup before acquisition, and keep setup, cancellation, and external process-exit budgets distinct.

2026-09-11 — Chat setup retained a disconnected identity promise #

  • Affected area: Native owner and conversation client setup in tests/chat-ui.test.mjs.
  • Symptom signature: The browser job fails before its first scenario with owner identity timed out after 15000ms.
  • Root cause: The fixture captured client.ready once. Agents replaces that promise on socket close, so losing the first identity leaves the captured promise pending even after native reconnect receives identity. A controlled identity-frame drop proves this failure mechanism; the original CI transport trigger was not recorded and remains unconfirmed.
  • Resolution: Reuse the execution fixture's native lifecycle follower for owner and conversation readiness. Preserve the existing 15-second chat deadline, cancel the follower on timeout or parent abort, and retain safe connection-state diagnostics without changing SDK reconnect or backoff.
  • Regression signal: The startup-only chat variant drops the first identity for both real owner and conversation routes and requires changed readiness promises and normal cleanup. Restoring the original await fails with the exact 15-second owner timeout; the shared follower passes. Existing native execution cleanup probes cover cancellation and reconnect.
  • Prevention rule: Every native fixture readiness wait must follow the current transport generation and release its listeners when its caller stops waiting. A bounded await on the first promise does not track reconnect.

2026-09-11 — Mobile label assertion preceded the responsive sidebar render #

  • Affected area: The mobile model/effort case in tests/chat-ui.test.mjs.
  • Symptom signature: Immediately after resizing from desktop to 320px, the default model label fails its untruncated-text assertion; an unchanged baseline reproduces the failure.
  • Root cause: Viewport acknowledgement preceded Kumo's matchMedia/useSyncExternalStore mobile render. The desktop rail still occupied 260px, leaving a 60px composer and a zero-width label. Fonts were already loaded. After the responsive render, the composer occupied 320px and the label measured 118.6875px with equal 119px client/scroll widths. This was neither fractional rounding nor persistent label truncation.
  • Resolution: Wait for the native sidebar's mobile DOM state before the immediate label-containment assertion at 320px and 390px. Keep the exact label invariant and existing deadlines; do not add a fixed sleep or change product CSS.
  • Regression signal: The original focused model/effort case fails on the unchanged build. With the native mobile-state wait, the same full case passes, including its narrow viewport, keyboard selection and tap-target assertions. Settled screenshots show the complete default label at both widths.
  • Prevention rule: A completed viewport command does not establish that a framework's responsive render has committed. Await the relevant native component state before measuring the resulting layout. This observation does not establish the cause of a separate scrolling or keyboard failure.

2026-09-11 — Installation fixture requests need complete responses and safe failure state #

  • Affected area: tests/installation-status.test.mjs and tests/orchestrator.test.mjs, accepted HTTP exchanges and frozen upgrade replay.
  • Symptom signature: CI received HTTP 400 during the 47-reservation pagination setup; another run could not find Continue saved request after reloading an unsubmitted upgrade.
  • Finding: Pagination checked response headers without consuming accepted bodies, contrary to the earlier native HTTP lifecycle lesson. Neither CI failure reproduced in the full native replay; their exact causes remain unconfirmed.
  • Resolution: Consume and validate each reservation's reserved response before the next request; also complete orchestrator account-selection and upgrade responses, including the original retry response rather than only its clone. Failure messages include only round, status, normalized content type and an allowlisted error code. Frozen replay reports only boolean/count state on failure, never stored intent, DOM text or identifiers. Preserve all requests, assertions and timing.
  • Regression signal: Run CI=true node --test --test-reporter=tap tests/installation-status.test.mjs; it retains 47 actual reservations, frozen target replay and the held native upgrade response. Run CI=true node --test --test-reporter=tap tests/orchestrator.test.mjs for all 21 native checks. The recurring operation-poll HTTP 500 cause also remains unconfirmed.
  • Prevention rule: Finish accepted HTTP exchanges before proceeding, and capture safe state at a missing-control boundary without assuming a timing increase repairs the underlying state.

2026-09-11 — Linux chat failures lacked terminal state evidence #

  • Symptom signature: CI intermittently fails the ArrowUp reading-position invariant or the owner page's readiness after observer Stop, completed Retry, and reload. The separate viewport-clamp case passes; focused local replays pass.
  • Diagnosis limit: The Linux causes remain unconfirmed. An enabled-attachment timeout does not identify connection, reconciliation, or stream state, and a scroll-position assertion does not distinguish native animation, layout changes, and application writes.
  • Instrumentation: The two cases now emit bounded failure-only connection/protocol flags, fixed DOM state, and optional numeric keyboard/scroll geometry. They exclude message contents, raw frames, identifiers, URLs, headers, and error text. Listeners and scroll interception are disposed per case.
  • Prevention rule: Capture the failing state before changing recovery or scroll policy. Preserve existing assertions and timings; passing local replays do not establish a CI fix.

2026-09-11 — Native fixture timeouts left browser, proxy and Worker resources alive #

  • Affected area: Diagnostics, conversation scheduling and two-site bridge test fixtures.
  • Symptom signature: Node reports the existing 90-second, 180-second or 360-second test timeout, but the job remains alive until workflow cancellation. The original suspended operation is not identified by those CI logs; diagnostics had already passed initial readiness, and bridge has no AgentClient readiness await.
  • Root cause: These fixtures owned native resource disposal only in the suspended test body's finally. A timeout does not guarantee that body unwinds. This is a proven cleanup defect, not a diagnosis of the original timeout trigger.
  • Resolution: Register independent, idempotent cleanup before acquisition. Own clients, browsers, HTTP requests, proxy sockets, Workers and temporary directories; dispose late acquisitions, wait for in-flight releases, and share one total cleanup deadline. Polls, streams and HTTP follow parent cancellation. Diagnostics and scheduling reuse the existing current-generation native readiness helper without changing SDK reconnect behavior or scenario deadlines.
  • Regression signal: The existing three fixture commands now run native subprocess probes holding actual identity delivery, stream delivery and HTTP response consumption after acquisition. Each starts a short Node timeout only after its held boundary. Removing only the independent cleanup hook makes the identity probe require the failing external watchdog; restoring it makes all three exit ordinarily with the intentional timeout. The probes deliberately do not forward their child signal into the held wait, so body-finally cleanup alone cannot satisfy them.
  • Prevention rule: Verify resource release independently of signal-driven body unwinding. Keep subprocess output bounded and report only fixed phase/resource labels and readiness counters, never native payloads or credentials.

2026-09-10 — Catalog identity checks ran after optional metadata validation #

  • Symptom signature: A duplicate tool with an oversized description leaves its sibling cataloged and retaining a prior decision. Reordering a schema's required/enum sets also clears an unchanged choice.
  • Root cause: Duplicate detection only considered entries that passed metadata validation, and fingerprints treated all JSON arrays as ordered data.
  • Resolution: Claim native identity before validating optional metadata, so every duplicate invalidates that identity. Normalize set-valued schema keywords only for semantic fingerprints; preserve ordered defaults, examples and tuple positions.
  • Regression signal: Catalog tests exercise malformed duplicates in either order, three-way duplicates, equivalent required/enum permutations, and changed ordered defaults.

2026-09-10 — Late MCP observations restored disabled tool choices #

  • Symptom signature: Disabling a tool during discovery succeeds, then a failed discovery restores its prior enabled choice without changing the accepted choices revision.
  • Root cause: Transport observations carried an old catalog including local settings; their transport revision remained valid after an independent choice update.
  • Resolution: Failure/health observations preserve the stored catalog. Successful discovery reconciles definitions against the latest persisted choices inside the same synchronous transaction. Discovery budgets reserve choice metadata.
  • Regression signal: A native gated discovery fails after an accepted disable; the persisted choice and subsequent healthy model-definition boundary must remain disabled.

2026-09-11 — Observer reload diagnostics missed client bootstrap failures #

  • Symptom signature: After observer Stop and completed Retry, Linux CI receives the owner reload document but remains on an empty conversation with disabled Attach. No subsequent history response, socket, busy state, or reconnect notice is recorded.
  • Diagnosis limit: The cause remains unconfirmed. Bounded native replays pass. The generated Octane bootstrap catches import/pre-hydrate/hydration errors and logs them to the console, so a page-error listener alone cannot observe every startup failure.
  • Instrumentation: Extend the existing bounded, failure-only helper with fixed request-start/script lifecycle and bootstrap-console categories plus document readiness, visibility, focus and ClientOnly fallback flags. Preserve all original assertions and timings; omit raw messages, arguments, URLs and identifiers.
  • Prevention rule: Distinguish an unstarted client from a mounted session's failed connection before changing startup or reconnect policy.

2026-09-10 — Durable approvals outlived conversation clear and lost accepted decisions #

  • Symptom signature: A cleared conversation still lists an old approval, or approving/releasing an old action recreates cleared text. A restart after approval starts execution loses approvalDecision. With 501 registered actions, a paused action is recorded as succeeded.
  • Root cause: Think 0.17.0 kept durable action pending rows across reset and treated missing transcript parts as compaction when applying late outcomes. Application activity recorded decisions only after the SDK finished execution, and an independent 500-entry presentation cache evicted still-registered actions.
  • Resolution: Extend the pinned SDK patch at its native reset/claim/outcome boundaries. Reset invalidates pending rows and execution lifetime; accepted decisions synchronously notify a public protected hook after the atomic claim. KeepAlive callbacks can begin after an await, so connectionless continuation carries the original lifetime into that callback and cannot answer a new post-clear message. Flarebot persists approved/running or denied/cancelled activity at that hook. The complete current native action registry owns activity descriptors.
  • Regression signal: Native Think tests cover RPC and WebSocket clear, delayed authorization/approval/execution, late history writes, restart after execution admission, and early actions in a 501-action registry. Same-generation compaction still receives its native outcome.
  • Prevention rule: Pending decisions, execution grants and outcome application must share the conversation reset boundary. A known owner decision is separate from an uncertain execution outcome; persist it before side effects. Never evict correctness metadata using an unrelated presentation-cache size.

2026-09-10 — Skill upload mutation read the previous file selection #

  • Affected area: src/routes/SkillInstall.tsx, TanStack mutation inputs.
  • Symptom signature: Selecting a valid file and immediately choosing Review upload showed its filename but reported that no package was selected; no review HTTP request was sent. Reloading made the timing-dependent failure easier to reproduce.
  • Root cause: The Octane TanStack adapter updates the mutation observer's options in an effect. A submit could therefore call the preceding render's mutation function, whose closure still held file = null.
  • Resolution: Pass the selected File or URL and lifecycle generation as mutation variables at submission.
  • Regression signal: The packaged Settings helper selects and immediately submits real files, repeats after reload, and requires successful native review responses before confirmation.
  • Prevention rule: Capture submitted user input in mutation variables; do not depend on effect-delayed observer options to refresh a mutation function's state closure.

2026-09-10 — URL review validation hid a generator shadowing error #

  • Affected area: worker/skill-installer.ts, direct package URL review.
  • Symptom signature: Every valid public HTTPS package URL returns invalid_url without making an outbound request, while upload review succeeds and TypeScript passes.
  • Root cause: A later generator-local value declaration shadowed the method's input parameter; the earlier validation closure read it in its temporal dead zone. Error normalization hid the resulting ReferenceError as invalid input.
  • Resolution: Give downloaded package data a distinct binding from the URL request input.
  • Regression signal: The native installer suite uses the real Think fetch tool with only outbound responses supplied by the fixture; it requires a valid URL to reach transport and produce a review before any R2 write.
  • Prevention rule: Keep request inputs and decoded responses distinct in Effect generators. Exercise successful transport paths, since rejected-input checks and typechecking cannot prove the validation closure reads the intended binding.

2026-09-10 — A review snapshot had no native idle lifetime #

  • Affected area: worker/skill-installer.ts, owner Skill package review.
  • Symptom signature: A still-unexpired review is lost if the native Agent is reconstructed while the owner reads it. The original native fixture had no lifetime alarm after review publication.
  • Root cause: The memory-only snapshot had neither a hibernation blocker nor an inactivity lease. A timer prevents hibernation, but the documented 70–140 second inactivity eviction is separate; native Agent keepAlive() supplies alarm heartbeats, but its default 30-second interval alone does not block ten-second hibernation.
  • Resolution: Acquire the public Agent keepAlive() lease and retain one expiry timer for the five-minute review. Release both on replacement, consumption or expiry, and release a late acquisition if its request was cancelled or superseded. Keep the snapshot unpublished until both its generation and lifetime are current. No package is written to R2 before confirmation.
  • Regression signal: Native tests require an armed/rearmed SDK alarm during 31 seconds without requests, then install the same disabled snapshot. They exercise a real workerd expiry callback, lease acquisition failure, late acquisition, replacement and consumption cleanup. A deliberate runtime restart still invalidates the review. These checks establish local native lifecycle ownership; they do not claim an automatic deployed-edge hibernation reproduction.
  • Prevention rule: Distinguish hibernation from inactivity eviction when choosing ownership for bounded in-memory sessions. See the Cloudflare Durable Object lifecycle and the pinned Agents keepAlive() contract.

2026-09-10 — Rejected POSTs raced pooled local HTTP socket reuse #

  • Affected area: tests/runtime.test.mjs, native workerd HTTP requests made by Node fetch.
  • Symptom signature: PR core-control fails before receiving an invalid-origin Skill POST response, with read ECONNRESET or UND_ERR_SOCKET. Different runs fail on different Skill routes; the expected authorization status itself is not contradicted.
  • Root cause: Safe Undici lifecycle tracing showed a socket receive HTTP 403 for one body-bearing POST, then be reused for the next POST and close with a socket error. Local workerd closes these early-denial connections without a Connection: close response header. Repeating the original denial loop reproduces the race in seconds. Consuming the earlier accepted response still fails, unlike the separate unread-response lesson above; removing POST bodies or requesting connection closure prevents the reproduced failure.
  • Resolution: The local fixture's runtimeFetch requests Connection: close, preserving each request body, response assertion and error without retries. Production authorization and native WebSocket connections are unchanged.
  • Regression signal: The permanent runtime test repeats body-bearing invalid-origin Skill POSTs for 100 rounds per origin. It failed at round 28 on /api/skills/manage before the fix, then the complete native routing/state/restart suite passed with it. The original sequence without extra rounds also passes.
  • Prevention rule: Distinguish a rejected application request from failure to receive its response. Diagnose HTTP connection lifecycle at the fixture boundary; never weaken authorization or replay POSTs to hide a local pooled-socket race.

2026-09-10 — Skill dialog height ignored Kumo's fixed top offset #

  • Affected area: .skill-install-dialog in src/styles.css.
  • Symptom signature: A tall package details dialog extends below the desktop viewport and clips its footer even though its buttons remain reachable in interaction tests.
  • Root cause: Kumo places dialogs 4rem below the desktop viewport top and 2rem below the mobile top. The custom height limit subtracted only 2rem from the viewport, so the positioned box could exceed its bottom edge.
  • Resolution: Subtract 8rem on desktop and 4rem on mobile, preserving internal vertical scrolling and a matching bottom margin.
  • Regression signal: The packaged management fixture's desktop bounds assertion fails against the original assets. It checks viewport containment and footer access by internal scrolling at desktop, 390×844, and 390×420; screenshots wait for the real opening animation. The reduced-height Chromium viewport does not reproduce an actual iOS keyboard.
  • Prevention rule: Derive modal height limits from the primitive's positioning offsets, and test its bounding box before scrolling. Successful button clicks alone do not prove a dialog fits on screen.

2026-09-11 — Native bridge reset lacked request lifecycle context #

  • Symptom signature: The complete native bridge test fails with ECONNRESET during an HTTP await, while its independent cleanup probe passes and the process exits normally.
  • Diagnosis limit: Node HTTP already buffers accepted responses through end. The unchanged native flow and 256 denied-POST/GET pairs passed locally, including 512 reused-socket requests; the Linux reset trigger remains unconfirmed.
  • Instrumentation: Report only fixed target/method/path categories, allowlisted error codes, socket/request/response lifecycle flags and the previous response status on that socket. Exclude raw errors, request paths, identifiers, headers and bodies.
  • Response lifecycle correction: Native partial-response termination emits aborted before the response error carrying ECONNRESET. One helper owns request/response rejection: it records abort, settles on the coded error, and falls back on premature close. The older raw response listeners are removed. Permanent request/response canaries and a real truncated Node response cover safe settlement; this does not establish the original Linux reset trigger.
  • Prevention rule: Distinguish transport failures from cleanup hangs. Capture the failed exchange before changing socket policy, retries or scenario deadlines.

2026-09-10 — Golden inference fixture rejected the native Skill tool #

  • Affected area: tests/fixtures/golden-customer-worker.ts, the golden-path inference boundary.
  • Symptom signature: The first remember request times out waiting for its saved reply after installation and model configuration succeed. The native provider diagnostic reports a one-millisecond Workers AI request failure; web and Docker shell tests pass.
  • Root cause: The fixture's exact tool-name assertion retained the pre-Skills application catalog. Think correctly supplied activate_skill, so the scripted model threw Unexpected application tools; production error normalization hid that synthetic fixture message.
  • Resolution: Include the newly enabled native Skill tool in the exact expected catalog. Preserve strict equality so unexpected or missing tools still fail.
  • Regression signal: A focused native Think probe passed the actual production tool registry into the unchanged golden inference model and reproduced both the exact assertion and the normalized provider error. It passes after updating the expected catalog. The connected golden-path test retains its original remember-and-reply assertion.
  • Prevention rule: When adding model-visible native tools, update strict inference-fixture catalogs and verify the actual model boundary before attributing normalized provider errors to credentials or infrastructure.

2026-09-10 — Truncation metadata changed the output budget #

  • Affected area: Skill resource model-output projection.
  • Symptom signature: A resource whose complete JSON result is 65,537 bytes returns every content byte with truncated: true.
  • Root cause: Replacing JSON false with true saves one byte, so a binary search could retain the complete content while meeting the 65,536-byte limit.
  • Resolution: Once truncation is required, the search must remove at least one content unit before preserving Unicode or base64 boundaries.
  • Regression signal: The native resource test constructs an exact 65,537-byte result, requires output within the limit, and verifies that a true truncation flag means content was removed.

2026-09-10 — Restored downstream choices revived an old Skill grant #

  • Affected area: SkillExecutionPermissions, downstream MCP authority.
  • Symptom signature: A held external operation commits after Allow → Never → Allow, even with a final grant check. Definition fingerprints remain unchanged across owner choices.
  • Root cause: The invocation compared current metadata and policy without retaining the downstream authority revision at admission.
  • Resolution: Capture trusted downstream authority revisions for the invocation and compare them at every grant boundary. MCP authority combines connection-attempt and tool-settings revisions with a persisted observation epoch; package declarations retain definition fingerprints. Accepted observations advance the epoch when availability or definitions change, preserving connection-attempt CAS. Permanently retire a grant after an observed failed check.
  • Regression signal: Native tests reject policy/enablement restoration, suppress a delayed read without an internal check, and prevent reuse after a caught check failure. A real OAuth sequence redeems two older unused callbacks around discovery failure/recovery: ready → error → ready keeps the same connection attempt and one token exchange, but must reject held Skill work with zero effects. SQLite checks cover definition restoration, unchanged/stale observations, legacy decoding and restart persistence.
  • Prevention rule: Revocation belongs to a non-reusable authority lifetime. Checking that access is allowed again cannot validate an earlier invocation.

2026-09-10 — Native Skill proxies serialized oversized arguments before host validation #

  • Symptom signature: A script sends a 1 MiB tool input; the host callback runs once even though its input schema rejects the request.
  • Root cause: The native runner serializes arbitrary arguments into RPC before the host adapter can validate them. Passing native workspace/tool objects through also exposes that unbounded serialization path.
  • Resolution: Expose only trusted local wrappers. Clone JSON own data properties without invoking accessors, reject unsupported or prototype-sensitive values, and bound the complete encoded host arguments to 32 KiB before native RPC. Check the pinned native codec's relevant intrinsics before its second serialization so user module mutation cannot expand the payload afterward.
  • Regression signal: The native runner test records zero host callbacks for oversized inputs, getters, custom serialization and codec mutation. Valid bounded tool/workspace calls still pass; raw fetch and TCP remain blocked.
  • Prevention rule: Bound hostile data before crossing the isolation boundary. A host-side schema cannot prevent an oversized request from reaching that host.

2026-09-10 — Script log utility classes were absent from packaged CSS #

  • Symptom signature: Expanded script stdout/stderr extends past a 375px viewport even though document and enclosing-region overflow checks pass. Chromium reports a 25,310px preformatted stream inside a 295px grid.
  • Root cause: The imported Kumo stylesheet does not generate every utility used by application JSX. Missing wrapping, scrolling and height utilities left the new log elements with browser-default white-space: pre and visible overflow.
  • Resolution: Give script streams a scoped stylesheet rule for width constraints, wrapping, scrolling and bounded height; use generated utilities only where their rules exist.
  • Regression signal: The packaged native chat scenario expands inert script output with a long unbroken line and checks each stream's bounding box on mobile, then collapses and reloads the message.
  • Prevention rule: Verify computed styles in the packaged browser. Class names and document-level overflow assertions do not prove a nested log container is bounded.

2026-09-10 — A shared policy editor submitted an older rendered snapshot #

  • Symptom signature: A global policy save completes and refreshes the shared query, then another control submits revision 0 while the server already holds revision 1.
  • Root cause: The choice callback constructed its whole update from a render-local snapshot. Another editor could refresh the shared query before that callback was replaced. Native CAS correctly rejected the stale submission.
  • Resolution: Apply the selected field edit once to the current successful, idle, scoped Query snapshot at user action. Keep connection/epoch fencing and server CAS; do not retry rejected writes automatically.
  • Regression signal: A packaged run recorded the old submitted revision. Interleaving coverage holds a global reply while another selector is open, then verifies current defaults and unrelated source overrides survive its edit. That new interleaving also passed the old build, so it is coverage rather than a deterministic reproduction of the timing-dependent failure.

2026-09-10 — Formatted JSX added whitespace inside a policy label #

  • Symptom signature: Saving a server Ask override updates native policy and the server selector, but the expected effective-policy label cannot be found.
  • Root cause: Splitting JSX immediately after the opening parenthesis rendered an extra space, producing Ask ( server override). Saved RPC data and actual DOM text ruled out a policy or reactivity failure.
  • Resolution: Render punctuation-sensitive policy labels as one complete string.
  • Regression signal: The packaged browser assertion requires the exact effective policy and its source after global, server and tool changes.

2026-09-10 — Imperative Skill retries extended private query retention #

  • Symptom signature: Retrying a failed Skill file succeeds, but closing and reopening the detail dialog reuses those bytes without another request.
  • Root cause: The retry used fetchQuery without the original query's gcTime: 0. TanStack adopts the longer garbage-collection interval, so the imperative retry silently retained private inspection/file data under the global cache default.
  • Resolution: Recovered inspection and file reads explicitly retain gcTime: 0; the authoritative inventory query keeps its ordinary metadata cache policy. Identity, connection, epoch and cancellation checks still fence each read.
  • Regression signal: The packaged Extensions test retries a failed read, closes and reopens the dialog, and requires a fresh request; a replaced Skill or offline transition cannot publish an old retry result.
  • Prevention rule: Imperative Query operations must preserve privacy-related retention options, not only the key and fetch function.

2026-09-10 — Removed MCP servers retained unreachable approval overrides #

  • Symptom signature: Repeatedly adding an MCP server, setting a server policy and removing it fills the 128-source override limit; a new server's policy cannot be saved through Extensions.
  • Root cause: Canonical server deletion removed only the connection row. Its separately stored policy override survived, and the UI had no row through which to clear that deleted identity.
  • Resolution: Remove source overrides inside the connection's SQLite deletion transaction and advance policy revision even when no override existed, rejecting editors opened before deletion. Startup prunes legacy orphan MCP overrides while preserving live MCP and all Skill overrides. Deletion refreshes both inventory and shared policy queries.
  • Regression signal: Native tests cover 129 complete lifecycles, preservation of a concurrent unrelated edit, stale editor rejection, policy-write failure rolling back the connection deletion, owner isolation, and one-time cleanup across process restarts. The packaged browser sets a server override, removes the server and immediately saves a global policy through real owner RPC.
  • Prevention rule: Persistent references need the same retirement boundary as their canonical source, including revision invalidation for writes that have not yet been submitted.

2026-09-11 — An open policy selector retained a fetching-render save guard #

  • Affected area: useCapabilityPolicyEditor and the Extensions policy selector interleaving fixture.
  • Symptom signature: A global Allow save finishes while a source selector is open; choosing Ask closes the menu but sends no source update, shows no error, and leaves the effective server-override label absent.
  • Root cause: The retained callback checked render-local locked before consulting current Query state. Controlled native browser instrumentation recorded locked: true with current Query success/idle at revision 1, no own pending/error mutation, and valid connection/epoch/abort checks. The original CI event scheduling was not captured; this proves the matching failure mechanism rather than every original timing detail.
  • Resolution: Use current connection and authoritative Query admission checks plus a ref-owned pending promise/error latch. Reset that latch only for a new connection generation or explicit error reload; an earlier generation's settlement cannot alter the current latch. Keep rendered locking for presentation, native CAS and explicit recovery; do not retry writes automatically.
  • Regression signal: The packaged MCP helper holds the global policy refresh until the open source trigger is disabled, then dispatches the real Kumo option click at the first saved-notice DOM update. The unchanged release fails the exact label assertion; the corrected release saves revision 1 while preserving global Allow and unrelated overrides. Existing global/source/tool precedence, conflict/reload, disabled-tool and offline checks remain in the same helper.
  • Prevention rule: A retained event callback must evaluate mutable admission state at action time. Reading fresh data after an earlier stale guard does not make the action current. Test the earliest event boundary rather than waiting long enough for every callback to refresh.
  • Fixture lifetime: A Promise returned by locator.evaluate does not inherit Playwright's action timeout. The disabled prerequisite now uses a five-second locator condition; the independent missing-transition check requires both held replies released and the page closed before its parent deadline.

2026-09-10 — Background refresh replaced a scrolled health history #

  • Symptom signature: The recent extension list disappeared during each automatic 15-second poll, losing the owner's reading position.
  • Root cause: The render condition treated every Query isFetching state as initial loading even when successful data still belonged to the current connection.
  • Resolution: Keep valid current history mounted with a refreshing status. Preserve zero retention, connection lifetime keys, and hiding after errors or connection loss.
  • Regression signal: A real browser wheels through native history and holds the actual periodic RPC reply. The old implementation removes the list; the corrected implementation preserves its DOM node and scrollTop before and after delivery, while existing reconnect fences still pass.
  • Prevention rule: Separate initial loading from background revalidation; a refresh should not destroy valid reading state.

2026-09-10 — Extension read adapters erased diagnostic classifications #

  • Symptom signature: An invalid Skill read and an enabled package's R2 failure both exported unavailable; a restored MCP protocol discovery failure exported connection while explicit refresh exported discovery.
  • Root cause: Skill observation ran after the public Promise/null adapter discarded typed failures and reconstructed provenance with a second inventory scan. MCP restoration reused its coarse recovery status as a diagnostic stage.
  • Resolution: Observe Skill validation, authoritative identity and storage access inside PersonalSkills before adapting the result to null. Preserve fixed validation/storage/unavailable/timeout categories and native fromManifest projection. Classify the known completed-transport/failed-discovery restoration branch separately from its existing retry state.
  • Regression signal: Native canary tests fail on the old resource classification and pass with distinct invalid-input, disabled, R2 and successful-read cases. Persisted MCP restart with a protocol discovery error is RED→GREEN for discovery while connection_failed/retryAt remain unchanged; restored transport failure remains connection.
  • Prevention rule: Classify structured failures at their authoritative boundary before generic adapters erase information; presentation classification must not silently change recovery policy.
  • Affected area: Conversation provenance links into Extensions settings.
  • Symptom signature: A historical link to a removed source focuses its settings group, but the removal explanation is outside the viewport when many other sources remain.
  • Root cause: Centering the entire inventory group scrolls past the notice near its beginning. Section intersection and ordinary element visibility assertions do not prove that the explanation is on screen.
  • Resolution: Give each removal notice a stable focus target and select it in the shared extension navigation hook. Keep normal source links focused on their exact inventory row.
  • Regression signal: A native owner fixture with eight surviving MCPs and Skills placed the original notice at y=-501.6 on desktop. The corrected fixture waits for actual focus, then requires the entire notice below the sticky header and within the viewport at 1280px and 375px; periodic inventory refresh still preserves user focus.
  • Prevention rule: Assert visibility of the user's destination or explanation, not merely intersection of its potentially large ancestor.

2026-09-11 — MCP fixture rejected native protocol negotiation #

  • Affected area: The v0.2 golden-path external MCP protocol fixture.
  • Symptom signature: Saving valid bearer credentials leaves the connection in error before discovery, while the fixture records one authenticated server/discover request.
  • Root cause: The pinned native Agents client probes a newer protocol before legacy initialization. The older-protocol fixture returned HTTP 500 for that unsupported method, preventing native negotiation from falling back.
  • Resolution: Return JSON-RPC -32601 (method not found) for the explicit unsupported probe. Keep native client negotiation, authentication and discovery unchanged.
  • Regression signal: A native runtime probe changes from connection_failed after one request to ready with all four capabilities through server/discover, initialize, tools/list and resources/list. The connected golden path retains actual token setup and discovery assertions.
  • Prevention rule: External protocol fixtures must represent unsupported-method semantics accurately; an implementation assertion is not a protocol response.

2026-09-11 — Extension navigation must retain notices and wait for layout #

  • Affected area: Deep links to removed MCPs and Skills in Extensions.
  • Symptom signature: The next MCP poll removes a focused notice; independently, a Skill notice can move below the viewport when the preceding MCP inventory finishes loading.
  • Root cause: Notice rendering treated background fetching as absence of confirmed data, while focus waited only for its own inventory and could precede an earlier section's layout.
  • Resolution: Keep valid notices mounted during refresh and defer new focus until the page's existing inventory and policy queries settle. Extensions owns the existing Availability query observers and shares their pending state; the query cache remains canonical.
  • Regression signal: Held native replies separately reproduce notice removal during the real 15-second poll and premature Skill focus before MCP delivery. The combined native helper passes desktop/mobile notice bounds, preceding-inventory gating, and unchanged DOM node/keyboard focus through refresh.
  • Prevention rule: Separate retained query data from fetching status, and coordinate navigation with the layout that can move its destination.

2026-09-11 — Long Skill versions overflowed the mobile review dialog #

  • Affected area: Skill headings and Kumo version badges.
  • Symptom signature: A full commit-based version extended to x478 on a 375px viewport even though the heading flex row wrapped.
  • Root cause: The Kumo Badge itself used white-space: nowrap and could not shrink. Measuring its display: contents child returned a zero-sized rectangle and missed the overflow.
  • Resolution: Bound Skill-heading badges to their container and allow text wrapping. Measure the actual Badge element with nonzero bounds in the native browser regression.
  • Prevention rule: Wrapping a flex row does not constrain an indivisible child. Exercise valid long versions and measure the rendered box, not a display-only text wrapper.

2026-09-11 — Native text decoding removed GitHub Markdown byte-order marks #

  • Affected area: GitHub Skill acquisition through native Think fetch.
  • Symptom signature: A valid BOM-prefixed Markdown reference caused invalid_package even though its tree size was within limits.
  • Root cause: Native Think fetch uses the default TextDecoder, which consumes a leading UTF-8 BOM. Comparing re-encoded text length against the original Git tree size therefore failed.
  • Resolution: Verify decoded candidates against the immutable Git blob checksum and byte count, restoring a consumed BOM only when it exactly matches that blob. Normalize a root BOM before native frontmatter parsing.
  • Regression signal: Native GitHub review/install/read preserves BOM references, accepts root BOMs with supplied or generated versions, and rejects same-size content substitution and invalid UTF-8.