Skip to content

fix(agent): keep the request prefix a Claude 5.x thinking block is bound to - #1151

Open
TheTuso wants to merge 3 commits into
mainfrom
piotrek/eng-984-agent-anthropic-claude-5x-rejects-replayed-thinking-block
Open

TheTuso wants to merge 3 commits into
mainfrom
piotrek/eng-984-agent-anthropic-claude-5x-rejects-replayed-thinking-block

Conversation

@TheTuso

@TheTuso TheTuso commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Closes ENG-984.

Claude Opus 5.5, Sonnet 5.5, and Fable 5.1 sign every thinking block over the request prefix that produced it, the system prompt and the messages before the block, and answer a replay after any edit to that prefix with:

messages.1.content.0: Invalid `signature` in `thinking` block. The block is bound to a different conversation.

The act loop edited that prefix in two places:

  • On the switch to auto after the provider refused or downgraded a forced tool choice, it appended the tool-calls-only rule to the system prompt. Recent @ai-sdk/anthropic versions and the AI Gateway downgrade with a warning instead of a 400, so turn 1 succeeds with a thinking block and turn 2 replays it under a changed prompt. This fires on the first step of every worker and is what the reporter hit; the issue text names only the second cause.
  • Screen and screenshot elision (transcript-compaction.ts) rewrites earlier messages once superseded screens pass their budget. Every block signed before the edit is invalid from then on.
flowchart LR
  A[turn n: history prepared] --> B{a message differs from<br/>the last request's object?}
  B -- no --> C[send as is]
  B -- yes --> D[afterEdit: drop reasoning parts<br/>from that message on<br/>Anthropic only]
  D --> E[note it on the turn] --> C
Loading
  • packages/e2e/src/agent/tool-loop.ts: the tool-calls-only rule is part of every step's instructions from turn 1, so the switch to auto changes only tool_choice, which the check ignores. prepareTurn compares what each message holds with what the last request sent, by value with provider options left out and binary payloads compared by bytes, so a replaced message and one a custom prepareMessages edits in place both show up while a moved cache breakpoint or a cloned history does not, and hands the first rewritten index to the provider hints. The overflow shrink now returns unchanged messages as the same objects for the same reason.
  • packages/e2e/src/agent/model/provider-hints.ts: afterEdit removes the reasoning parts of assistant messages from the rewritten one on, and drops a reply that was only reasoning. Anthropic allows removing thinking from the end of a history; a block produced after the elision stays valid because the elided message does not change again. Other providers get the history untouched, since OpenAI Responses needs its reasoning items beside their function calls. The Anthropic detection now also recognizes Claude on Bedrock (eu.anthropic.claude-…) and Vertex AI (vertex.anthropic.messages), where the same check runs; before, only the direct provider and the gateway were matched.
  • tests/unit/tool-loop-prefix.test.ts: the real @ai-sdk/anthropic provider over a fake fetch, asserting on the request bodies that the system prompt is byte-identical through the switch, that no thinking is replayed after an elided message while later thinking is, and that a non-Anthropic model keeps every reasoning part.
  • Not done: prefix_mismatch_behavior: drop_block. It needs a beta header, a provider version and a gateway that forward it, is rejected under Sonnet 5.5's between_tools, and hides the edit instead of removing it. The activeTools narrowing on forced-conclusion turns stays: Anthropic's docs list the tool set in the bound prefix, but a narrowed and a reordered list both passed the check when probed live, and main's forced turn on Fable 5.1 passed it under enforcement.

Verified

Ran it locally: yes

  • testbed, built CLI through the AI Gateway, binding check opted in as error (our account is not enforced by default) and effort: 'high' (at medium Opus 5.5 produced no thinking on these pages), all six tests-agent files, --no-cache:
model main this branch
claude-opus-5-5 3 of 29 fail with the 400 (keypad flow on model call 2, both drawn wizards) 29 pass
claude-sonnet-5-5 2 of 29 fail with the 400 (both drawn wizards) 29 pass
claude-fable-5-1 4 of 29 fail with the 400, 1 model-behavior failure 29 pass
main, claude-opus-5-5:
   × act enters a ten-digit code on a keypad painted on a canvas > agent.act "on the drawn keypad enter the code 3141592653 …" 24.99s · 2 model calls failed
 FAIL  |web| tests-agent/canvas-flow.e2e.ts > act enters a ten-digit code on a keypad painted on a canvas
MODEL_PROVIDER_FAILED: the model provider failed: messages.1.content.2: Invalid `signature` in `thinking` block. The block is bound to a different conversation. Remove the block, or set `thinking.block_binding.prefix_mismatch_behavior` to "drop_block".
 FAIL  |web| tests-agent/pixel-tools.e2e.ts > walks the drawn wizard through points
MODEL_PROVIDER_FAILED: the model provider failed: messages.3.content.0: Invalid `signature` in `thinking` block. The block is bound to a different conversation. …
 Test Files  3 failed | 3 passed (6)
      Tests  3 failed | 26 passed (29)

this branch, claude-opus-5-5:
 Test Files  6 passed (6)
      Tests  29 passed (29)
         AI  954.1k tokens · 83% cached · $1.16 · 103 model calls · gateway/anthropic/claude-opus-5-5
  • The branch runs recorded 4, 9, and 3 reasoning drops at elisions respectively, so the elision path ran under enforcement; the final Opus 5.5 run on the committed code passed 29 of 29 with 4 drops. e2e explore --max-steps 6 on Opus 5.5 completed 6 of 6 steps on the branch. A step driven into its forced-conclusion turns with a three-call budget passed the check on both builds.
  • Control: the testbed's default openai/gpt-6-luna-fast with the unmodified agent config, 28 of 29 passed and the keypad flow passed on two reruns.
  • Live probes through the gateway: an edited first message is rejected on Opus 5.5 with the opt-in and accepted without it; a narrowed or reordered tool list is accepted; Haiku 4.5 and Sonnet 4.6 with enabled thinking accept a last assistant turn with its thinking stripped.
  • pnpm bench:ab on the web benchmark's control inventory, 2 pairs: identical behavior and counters on every run of both builds, timings same.
  • Not run: Claude through Bedrock or Vertex AI; the wider detection is covered by unit cases only.

Fresh-context review: one finding rejected, that narrowing tools on forced turns breaks the check, since the live probe and main's forced turn under enforcement show it does not; one finding fixed, the Bedrock and Vertex detection above.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/e2e/src/agent/tool-loop.ts Outdated
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e web: 91 passed

All 91 tests in 41 files
Test Time
🟢 tests/aria-hidden-flow.e2e.ts · 2 passed 581ms
🟢 aria hidden flow › picks the Pro plan by its visible text and confirms it 393ms
🟢 aria hidden flow › another plan shows the error and keeps the picker open 188ms
🟢 tests/async-states.e2e.ts · 2 passed 7.2s
🟢 async states › reads the code off the transient toast and verifies it once the button enables 2.3s
🟢 async states › a wrong code is rejected after the toast has gone 4.9s
🟢 tests/canvas-only.e2e.ts · 2 passed 283ms
🟢 canvas only › taps square, circle, then diamond at their pixel coordinates 151ms
🟢 canvas only › a shape out of order paints the error and resets progress 132ms
🟢 tests/canvas-whiteboard.e2e.ts · 2 passed 384ms
🟢 canvas whiteboard › drags both shapes into their slots and saves 212ms
🟢 canvas whiteboard › bounces a shape off the wrong slot and refuses to save early 172ms
🟢 tests/cart-totals.e2e.ts · 2 passed 360ms
🟢 cart totals › increasing a quantity updates the line total but the order summary stays stale (planted bug) 207ms
🟢 cart totals › a quantity never drops below one 153ms
🟢 tests/checkout-review.e2e.ts · 1 passed 177ms
🟢 shows a raw greeting token and an undefined delivery date (planted bug), yet places the order 177ms
🟢 tests/context-menu-trap.e2e.ts · 2 passed 649ms
🟢 context menu trap › right-click opens nothing; the row menu renames the file 322ms
🟢 context menu trap › the decoy menu items refuse and keep the file 327ms
🟢 tests/control-inventory.e2e.ts · 8 passed 3.7s
🟢 control inventory › check sets the checkbox and the radio without flipping a set one 328ms
🟢 control inventory › check picks a radio the pick replaces with its summary 220ms
🟢 control inventory › a long press and a double click are told apart from a tap 1.0s
🟢 control inventory › the file menu opens on right-click only and Rename renames the file 250ms
🟢 control inventory › setInputFiles attaches files named from the project root 145ms
🟢 control inventory › the details view is left through the browser history 175ms
🟢 control inventory › scrollIntoView brings the footnote into view 280ms
🟢 control inventory › every exercise done shows the success message 1.3s
🟢 tests/css-content-ui.e2e.ts · 2 passed 405ms
🟢 css content ui › the tile labelled Ship it advances and confirming ships 239ms
🟢 css content ui › a wrong tile shows the CSS-rendered error and keeps the picker 166ms
🟢 tests/date-picker.e2e.ts · 2 passed 698ms
🟢 date picker › navigates three months ahead and books the required date 395ms
🟢 date picker › confirming without the exact date shows the error 303ms
🟢 tests/debounced-search.e2e.ts · 2 passed 2.8s
🟢 debounced search › a decoy one character off is refused 1.4s
🟢 debounced search › picking the exact item adds it to the cart 1.4s
🟢 tests/deferred-dialog.e2e.ts · 1 passed 1.1s
🟢 one submit opens one confirm, even when double-clicked 1.1s
🟢 tests/div-soup.e2e.ts · 2 passed 736ms
🟢 div soup › a wrong code is rejected 332ms
🟢 div soup › the right code grants access after the delete tile fixes a typo 404ms
🟢 tests/drag-and-drop.e2e.ts · 2 passed 701ms
🟢 drag and drop › an out-of-order item resets the dropzone 349ms
🟢 drag and drop › dropping Banana then Cherry passes 352ms
🟢 tests/filter-deep-link.e2e.ts · 2 passed 253ms
🟢 filter deep link › filtering writes the query to the URL 153ms
🟢 filter deep link › opening a shared link restores the filter 100ms
🟢 tests/gift-card-purchase.e2e.ts · 3 passed 629ms
🟢 gift card purchase › refuses to buy without an amount 146ms
🟢 gift card purchase › refuses a recipient without an at sign 249ms
🟢 gift card purchase › sends the gift card 234ms
🟢 tests/hover-menu.e2e.ts · 2 passed 526ms
🟢 hover menu › a decoy leaf is refused 250ms
🟢 hover menu › hovering Account, then Billing, reaches Redeem voucher 276ms
🟢 tests/iframe-form.e2e.ts · 2 passed 542ms
🟢 iframe form › a wrong code is refused by the host page 268ms
🟢 iframe form › the code read from one frame applies through the other 274ms
🟢 tests/image-only-ui.e2e.ts · 3 passed 502ms
🟢 image only ui › the icons have no names, so the bare svg locator is ambiguous 117ms
🟢 image only ui › a wrong icon shows the error image and keeps the icons 173ms
🟢 image only ui › tapping the star, then the heart, verifies the icons 212ms
🟢 tests/infinite-scroll.e2e.ts · 1 passed 3.7s
🟢 scrolls the feed until the Golden Ticket loads, then claims it 3.7s
🟢 tests/kanban-board.e2e.ts · 3 passed 749ms
🟢 kanban board › pointer drags move both cards into the goal layout 403ms
🟢 kanban board › a swipe along a path drags a card as well 146ms
🟢 kanban board › a plain tap moves nothing and the untouched board is rejected 200ms
🟢 tests/login-form.e2e.ts · 4 passed 852ms
🟢 login form › rejects a malformed email 196ms
🟢 login form › rejects a short password 204ms
🟢 login form › rejects an unknown account 196ms
🟢 login form › signs in and logs out 256ms
🟢 tests/lying-labels.e2e.ts · 4 passed 755ms
🟢 lying labels › the button whose accessible name says Pay now is the wrong one 166ms
🟢 lying labels › a text match for Pay now catches the decoy phrase too 129ms
🟢 lying labels › the field whose accessible name says Amount is the wrong one 248ms
🟢 lying labels › visible text drives the payment through 212ms
🟢 tests/native-dialogs.e2e.ts · 3 passed 505ms
🟢 native dialogs › answers the confirm, prompt, and alert chain 188ms
🟢 native dialogs › dismissing the confirm cancels the order 171ms
🟢 native dialogs › a wrong gift code is rejected 146ms
🟢 tests/newsletter-signup.e2e.ts · 1 passed 2.3s
🟢 subscribe renders no feedback and logs a TypeError (planted bug) 2.3s
🟢 tests/onboarding-wizard.e2e.ts · 5 passed 5.8s
🟢 onboarding wizard › back keeps what was typed and the brief comes back with it 337ms
🟢 onboarding wizard › the address step takes the brief through its bare-div dropdown 349ms
🟢 onboarding wizard › the profile step steers the calendar three decades back 1.5s
🟢 onboarding wizard › the preferences step flips the toggles the brief names and leaves the trap off 1.7s
🟢 onboarding wizard › the review step repeats the brief and finishes with the PIN 1.9s
🟢 tests/order-history.e2e.ts · 2 passed 1.5s
🟢 order history › overview and settings render their panels 199ms
🟢 order history › the orders tab never leaves its loading state (planted bug) 1.3s
🟢 tests/otp-auto-advance.e2e.ts · 3 passed 1.5s
🟢 otp auto-advance › focus advances with each digit and the full code verifies 509ms
🟢 otp auto-advance › backspace on an empty box clears and refocuses the previous one 164ms
🟢 otp auto-advance › a wrong code clears every box and starts over 785ms
🟢 tests/overlay-trap.e2e.ts · 1 passed 633ms
🟢 continues only after the banner and the overlay are gone 633ms
🟢 tests/playbook-cleanup.e2e.ts · 2 passed 574ms
🟢 playbook cleanup › deleting the draft removes it from an exact-name search 293ms
🟢 playbook cleanup › cancelling the dialog keeps the draft searchable 281ms
🟢 tests/price-sorting.e2e.ts · 3 passed 420ms
🟢 price sorting › opens in the featured order with sale prices struck through 121ms
🟢 price sorting › low to high sorts by the original price, not the paid one (planted bug) 145ms
🟢 price sorting › high to low is the same wrong order reversed (planted bug) 154ms
🟢 tests/product-reviews.e2e.ts · 1 passed 1.1s
🟢 the running-shoe page lists coffee-grinder reviews (planted bug) 1.1s
🟢 tests/promo-storefront.e2e.ts · 1 passed 1.4s
🟢 buys a deal once the promo chrome is out of the way 1.4s
🟢 tests/rich-text-editor.e2e.ts · 2 passed 486ms
🟢 rich text editor › bolding exactly the required word passes the check 215ms
🟢 rich text editor › bold that spills into the previous word fails the check 271ms
🟢 tests/saved-addresses.e2e.ts · 3 passed 649ms
🟢 saved addresses › adds an address and clears the form 243ms
🟢 saved addresses › ignores an add with an empty field 191ms
🟢 saved addresses › delete shows its toast but keeps the address, on retry too (planted bug) 215ms
🟢 tests/shadow-dom-form.e2e.ts · 2 passed 578ms
🟢 shadow DOM form › rejects a wrong access code 294ms
🟢 shadow DOM form › grants access with the code from the hint inside the root 284ms
🟢 tests/smoke.e2e.ts · 1 passed 342ms
🟢 home lists the scenarios and opens one 342ms
🟢 tests/stale-dom.e2e.ts · 1 passed 2.1s
🟢 taps the moving target three times across rebuilds of the list 2.1s
🟢 tests/sticky-chrome.e2e.ts · 1 passed 239ms
🟢 accepts the terms once the button clears the sticky footer 239ms
🟢 tests/virtualized-table.e2e.ts · 1 passed 43.1s
🟢 scrolls the windowed table to the Golden Row and claims it 43.1s
🟢 tests/wishlist.e2e.ts · 2 passed 1.6s
🟢 wishlist › saving toggles the button and the tab badge 254ms
🟢 wishlist › the wishlist tab renders the empty state despite the badge count (planted bug) 1.3s

e2e 0.21.1 · 1m 34s · web · run artifacts

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e web agent: 33 passed

34 agent steps · 26 replayed from cache · 21 model calls · 88.5k tokens (84% cached) · $0.0056

All 33 tests in 5 files
Test Agent Time
🟢 tests-agent/control-inventory.e2e.ts · 7 passed 7 steps 7.6s
🟢 control inventory › check sets the checkbox and the radio 1 step 646ms
🟢 control inventory › check picks a radio the pick replaces with its summary 1 step 292ms
🟢 control inventory › a long press and a double click are chosen over a plain tap 1 step 936ms
🟢 control inventory › a right-click opens the file menu and Rename renames the file 1 step 2.6s
🟢 control inventory › upload attaches the named project files 1 step 2.4s
🟢 control inventory › back returns from the details view to the inventory 1 step 315ms
🟢 control inventory › scroll_to brings the footnote into view 1 step 399ms
🟢 tests-agent/login-form.e2e.ts · 1 passed 1 step 365ms
🟢 act signs in with the benchmark account 1 step 365ms
🟢 tests-agent/planted-bugs.e2e.ts · 3 passed 4 steps · 8 calls 16.2s
🟢 cart totals: a judgment catches the stale order summary 2 steps · 4 calls 8.3s
🟢 checkout review: a judgment catches the broken copy 1 step · 1 call 2.1s
🟢 wishlist: a judgment catches the lost saved items 1 step · 3 calls 5.9s
🟢 tests-agent/scenarios.e2e.ts · 21 passed 21 steps · 13 calls 2m 33s
🟢 act completes shadow-dom-form 1 step · 3 calls 7.9s
🟢 act completes lying-labels 1 step · 4 calls 18.5s
🟢 act completes iframe-form 1 step · 3 calls 6.7s
🟢 act completes infinite-scroll 1 step 5.8s
🟢 act completes virtualized-table 1 step 1m 19s
🟢 act completes overlay-trap 1 step 934ms
🟢 act completes sticky-chrome 1 step 894ms
🟢 act completes async-states 1 step · 3 calls 9.2s
🟢 act completes debounced-search 1 step 2.4s
🟢 act completes hover-menu 1 step 521ms
🟢 act completes drag-and-drop 1 step 470ms
🟢 act completes kanban-board 1 step 651ms
🟢 act completes context-menu-trap 1 step 2.7s
🟢 act completes stale-dom 1 step 496ms
🟢 act completes otp-auto-advance 1 step 765ms
🟢 act completes date-picker 1 step 936ms
🟢 act completes rich-text-editor 1 step 10.1s
🟢 act completes playbook-cleanup 1 step 566ms
🟢 act completes promo-storefront 1 step 1.3s
🟢 act completes gift-card-purchase 1 step 2.4s
🟢 act filters the list and the URL carries the filter 1 step 258ms
🟢 tests-agent/unique-cache.e2e.ts · 1 passed 1 step 333ms
🟢 act adds an address with a run-unique label 1 step 333ms

e2e 0.21.1 · 2m 58s · web · run artifacts

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e ios: 53 passed, 1 skipped

All 54 tests in 7 files
Test Time
⏭️ tests/controls.e2e.ts · 23 passed, 1 skipped 3m 12s
🟢 control inventory › a label query resolves a labeled text field 8.8s
🟢 control inventory › a labeled field answers name, display value, value reads, fill, and clear 15.6s
🟢 control inventory › a placeholder query and attribute reads find the field 4.6s
🟢 control inventory › a secure field takes a fill and denies value reads 9.5s
🟢 control inventory › press sends a character and Enter through the soft keyboard 9.2s
🟢 control inventory › a field autofocuses on mount and focus() moves the focus 14.7s
🟢 control inventory › toBeFocused follows the focus 9.4s
🟢 control inventory › a switch checks, unchecks, and reports its state 10.4s
🟢 control inventory › checkbox and radio roles answer getByRole, and a checkbox toggles by tap 8.9s
🟢 control inventory › checkbox and radio roles check, uncheck, and report their state 13.7s
🟢 control inventory › a radio group reports the checked option 8.9s
🟢 control inventory › buttons report disabled, enabled, and a state that arrives later 8.4s
🟢 control inventory › a display: none element is absent rather than hidden, its visible twin attached 7.9s
🟢 control inventory › a header answers its accessible name 4.7s
🟢 control inventory › a tab row answers count, all, texts, filters, positions, and switching 7.6s
🟢 control inventory › a list answers counts, list-form text, reads, and boxes in order 8.5s
⏭️ control inventory › a row contains its texts (skipped: React Native flattens a plain View out of the accessibility tree on both platforms: XCTest and the Android helper report a View with a testID as a leaf beside its children, so filter({ has }) and a query scoped to it match nothing; only Sc…)
🟢 control inventory › a long press lands as one 9.4s
🟢 control inventory › pointer verbs land at the point asked for 11.5s
🟢 control inventory › an ambiguous locator fails at once 6.4s
🟢 control inventory › screenshot, restart, and clearState reopen the app on its home list 11.8s
🟢 refusals › verbs a device never declares are refused before the engine 2ms
🟢 refusals › keys the soft keyboard cannot send are refused by the engine 608ms
🟢 refusals › a missing locator fails as not found once its timeout passes 1.3s
🟢 tests/device-fixture.e2e.ts · 10 passed 1m 26s
🟢 device fixture › setAppearance flips the color scheme the app reads 11.7s
🟢 device fixture › setOrientation rotates the window the app measures 6.4s
🟢 device fixture › the clipboard reads back what was written 5.4s
🟢 device fixture › home leaves the app, openApp brings it back where it was 5.1s
🟢 device fixture › closeApp then openApp relaunches on the home list 7.6s
🟢 device fixture › closeApp leaves the next commands on the same device 11.7s
🟢 device fixture › foregroundApp after closeApp reports the closed session as APP_NOT_OPEN 8.1s
🟢 device fixture › device.back pops the scenario 7.9s
🟢 device fixture › setLocation and clearLocation change what the app reads 11.5s
🟢 device fixture › enrollBiometrics and setBiometrics answer a biometric prompt 10.9s
🟢 tests/device.e2e.ts · 2 passed 54.2s
🟢 sequential onboarding echoes the exact values 38.4s
🟢 onboarding rejects a malformed email before advancing 15.8s
🟢 tests/flows-lists.e2e.ts · 7 passed 3m 31s
🟢 infinite scroll reaches the target item 28.6s
🟢 product catalog adds exactly two of the right variant 11.2s
🟢 gestures: long-press, then swipe left 9.4s
🟢 sticky chrome: accept below the fold, then continue 11.3s
🟢 async states: load, pull to refresh, claim through the toast 11.3s
🟢 huge virtualized list reaches row 512 2m 6s
🟢 web view coupon form applies the code shown on the page 13.6s
🟢 tests/flows.e2e.ts · 7 passed 1m 15s
🟢 text inputs unlock the submit button 15.1s
🟢 filled text inputs report their values 11.2s
🟢 modal flow confirms through the native alert 10.5s
🟢 bottom tabs act inside the last tab 8.1s
🟢 error recovery retries, then confirms the delete 10.2s
🟢 choice controls place the exact order 8.4s
🟢 debounced search selects the target once results arrive 11.0s
🟢 tests/login-form.e2e.ts · 3 passed 49.1s
🟢 login form › rejects a malformed email 13.0s
🟢 login form › rejects an unknown account 13.2s
🟢 login form › signs in and logs out 23.0s
🟢 tests/smoke.e2e.ts · 1 passed 11.8s
🟢 home lists the scenarios and opens one 11.8s

e2e 0.21.1 · 5m 52s · ios-simulator · run artifacts

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e android agent: 20 passed

24 agent steps · 15 replayed from cache · 33 model calls · 162.8k tokens (86% cached) · $0.0094

All 20 tests in 1 file
Test Agent Time
🟢 tests-agent/scenarios.e2e.ts · 20 passed 24 steps · 33 calls 7m 19s
🟢 act completes Login Form 1 step 24.4s
🟢 act completes Infinite Scroll List 1 step 23.1s
🟢 act completes Modal Flow 1 step 9.8s
🟢 act completes Bottom Tabs 1 step 8.3s
🟢 act completes Text Input Variations 1 step 6.7s
🟢 act completes Broken Accessibility 2 steps · 5 calls 42.6s
🟢 act completes Vision Only 2 steps · 1 call 12.5s
🟢 act completes Gestures 1 step · 7 calls 52.9s
🟢 act completes Async States 1 step · 8 calls 44.1s
🟢 act completes Debounced Search 1 step 7.3s
🟢 act completes Huge Virtualized List 1 step 1m 5s
🟢 act completes Flattened Registration Form 2 steps · 1 call 12.0s
🟢 act completes Flattened Login 2 steps · 6 calls 45.5s
🟢 act completes Sticky Chrome Target 1 step 9.8s
🟢 act completes WebView Accessibility 1 step · 5 calls 21.6s
🟢 act completes Permission Prompt 1 step 7.6s
🟢 act completes Product Catalog 1 step 10.9s
🟢 act completes Error Recovery 1 step 10.0s
🟢 act completes Choice Controls 1 step 12.1s
🟢 act completes Sequential Onboarding 1 step 12.0s

e2e 0.21.1 · 7m 19s · android-emulator · run artifacts

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e android: 55 passed, 1 skipped

All 56 tests in 7 files
Test Time
⏭️ tests/controls.e2e.ts · 24 passed, 1 skipped 2m 50s
🟢 control inventory › a label query resolves a labeled text field 17.5s
🟢 control inventory › a labeled field answers name, display value, value reads, fill, and clear 12.1s
🟢 control inventory › a placeholder query and attribute reads find the field 5.2s
🟢 control inventory › a secure field takes a fill and denies value reads 5.6s
🟢 control inventory › press sends a character and Enter through the soft keyboard 8.0s
🟢 control inventory › a field autofocuses on mount and focus() moves the focus 12.0s
🟢 control inventory › toBeFocused follows the focus 9.8s
🟢 control inventory › a switch checks, unchecks, and reports its state 9.4s
🟢 control inventory › checkbox and radio roles answer getByRole, and a checkbox toggles by tap 6.6s
🟢 control inventory › checkbox and radio roles check, uncheck, and report their state 11.8s
🟢 control inventory › a tab role answers getByRole 3.8s
🟢 control inventory › a radio group reports the checked option 9.1s
🟢 control inventory › buttons report disabled, enabled, and a state that arrives later 8.4s
🟢 control inventory › a display: none element is absent rather than hidden, its visible twin attached 6.5s
🟢 control inventory › a header answers its accessible name 3.9s
🟢 control inventory › a tab row answers count, all, texts, filters, positions, and switching 5.4s
🟢 control inventory › a list answers counts, list-form text, reads, and boxes in order 5.3s
⏭️ control inventory › a row contains its texts (skipped: React Native flattens a plain View out of the accessibility tree on both platforms: XCTest and the Android helper report a View with a testID as a leaf beside its children, so filter({ has }) and a query scoped to it match nothing; only Sc…)
🟢 control inventory › a long press lands as one 6.8s
🟢 control inventory › a double tap lands inside the 300 ms window 7.6s
🟢 control inventory › an ambiguous locator fails at once 4.8s
🟢 control inventory › screenshot, restart, and clearState reopen the app on its home list 9.1s
🟢 refusals › verbs a device never declares are refused before the engine 2ms
🟢 refusals › keys the soft keyboard cannot send are refused by the engine 370ms
🟢 refusals › a missing locator fails as not found once its timeout passes 1.1s
🟢 tests/device-fixture.e2e.ts · 9 passed 1m 11s
🟢 device fixture › setOrientation rotates the window the app measures 4.8s
🟢 device fixture › home leaves the app, openApp brings it back where it was 6.4s
🟢 device fixture › foregroundApp sees the home screen after home() 4.4s
🟢 device fixture › closeApp then openApp relaunches on the home list 6.6s
🟢 device fixture › closeApp leaves the next commands on the same device 6.5s
🟢 device fixture › foregroundApp after closeApp reports the closed session as APP_NOT_OPEN 4.6s
🟢 device fixture › device.back pops the scenario 4.8s
🟢 device fixture › setNetwork and setAirplaneMode change what the app sees 15.1s
🟢 device fixture › setLocation and clearLocation change what the app reads 18.3s
🟢 tests/device.e2e.ts · 4 passed 36.1s
🟢 permission prompt › allowing the system dialog enables the microphone 6.4s
🟢 permission prompt › a denied prompt recovers once the permission is granted outside 12.7s
🟢 sequential onboarding echoes the exact values 10.3s
🟢 onboarding rejects a malformed email before advancing 6.7s
🟢 tests/flows-lists.e2e.ts · 7 passed 2m 0s
🟢 infinite scroll reaches the target item 15.9s
🟢 product catalog adds exactly two of the right variant 9.6s
🟢 gestures: long-press, then swipe left 7.7s
🟢 gestures: double-tap completes the flow 9.5s
🟢 sticky chrome: accept below the fold, then continue 11.0s
🟢 async states: load, pull to refresh, claim through the toast 11.6s
🟢 huge virtualized list reaches row 512 54.5s
🟢 tests/flows.e2e.ts · 7 passed 1m 1s
🟢 text inputs unlock the submit button 6.4s
🟢 filled text inputs report their values 5.7s
🟢 modal flow confirms through the native alert 8.8s
🟢 bottom tabs act inside the last tab 8.0s
🟢 error recovery retries, then confirms the delete 9.7s
🟢 choice controls place the exact order 11.2s
🟢 debounced search selects the target once results arrive 11.0s
🟢 tests/login-form.e2e.ts · 3 passed 29.1s
🟢 login form › rejects a malformed email 8.4s
🟢 login form › rejects an unknown account 8.4s
🟢 login form › signs in and logs out 12.3s
🟢 tests/smoke.e2e.ts · 1 passed 5.2s
🟢 home lists the scenarios and opens one 5.2s

e2e 0.21.1 · 8m 13s · android-emulator · run artifacts

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e ios agent: 21 passed, 1 skipped

25 agent steps · 10 replayed from cache · 69 model calls · 430.1k tokens (82% cached) · $0.0299

All 22 tests in 1 file
Test Agent Time
⏭️ tests-agent/scenarios.e2e.ts · 21 passed, 1 skipped 25 steps · 69 calls 14m 40s
🟢 act completes Login Form 1 step · 11 calls 1m 27s
🟢 act completes Infinite Scroll List 1 step 41.9s
🟢 act completes Modal Flow 1 step 15.6s
🟢 act completes Bottom Tabs 1 step 11.9s
🟢 act completes Text Input Variations 1 step 20.7s
🟢 act completes Broken Accessibility 2 steps · 6 calls 1m 1s
🟢 act completes Vision Only 2 steps · 6 calls 58.0s
🟢 act completes Gestures 1 step · 12 calls 1m 42s
🟢 act completes Async States 1 step · 6 calls 37.9s
🟢 act completes Debounced Search 1 step · 3 calls 35.1s
🟢 act completes Flattened Registration Form 2 steps · 1 call 37.0s
🟢 act completes Flattened Login 2 steps · 10 calls 1m 42s
🟢 act completes Sticky Chrome Target 1 step 19.2s
🟢 act completes WebView Accessibility 1 step 13.3s
🟢 act completes Permission Prompt 1 step · 2 calls 35.1s
🟢 act completes Photo Picker 1 step · 2 calls 33.9s
🟢 act completes Product Catalog 1 step 13.9s
🟢 act completes Error Recovery 1 step 14.5s
🟢 act completes Choice Controls 1 step · 5 calls 1m 4s
🟢 act completes Stripe PaymentSheet 1 step · 5 calls 1m 2s
🟢 act completes Apple Pay 1 step 15.4s
⏭️ act completes Apple Pay Billing Address (skipped: the CI simulator's Apple Pay sheet skips the billing-address step, so the scenario cannot be completed there)

e2e 0.21.1 · 14m 41s · ios-simulator · run artifacts

…und to

Claude Opus 5.5, Sonnet 5.5, and Fable 5.1 sign every thinking block over
the system prompt and the messages before it, and reject a replay after
either changed ("Invalid signature in thinking block. The block is bound to
a different conversation"). The act loop edited both mid-step: it appended
the tool-calls-only rule to the system prompt on the switch to auto after
the provider refused or downgraded a forced tool choice, and it elided
superseded screens and screenshots from earlier messages.

The rule is now part of every step's instructions from the first turn, so
the switch changes only tool_choice. When an earlier message is rewritten,
found by object identity against the last request, the Anthropic hints
drop the reasoning parts from that message on and the step's turn notes
it; other providers get the history untouched. The overflow shrink returns
unchanged messages as the same objects. The Anthropic detection also
recognizes Claude on Bedrock and Vertex AI, where the same check runs.

Verified live through the AI Gateway with the binding check opted in as
error: main fails the pixel flows on all three models, the branch passes
all 29 testbed agent tests on each.

Closes ENG-984.
The rewrite check compared message objects against the last request's, which an in-place edit through the object handed to prepareMessages leaves identical. It now keeps the references each message held (string content, text parts' strings, every other part) and compares those.
@TheTuso
TheTuso force-pushed the piotrek/eng-984-agent-anthropic-claude-5x-rejects-replayed-thinking-block branch from fa72312 to 428adbb Compare October 15, 2026 12:31
@okwasniewski

Copy link
Copy Markdown
Member

Deep review: root cause and approach are right (provider-based detection, drops at/after the already-elided message so no extra cache misses, identity-preserving compaction means default executor isn't flagged). Unit suite green, new Anthropic tests fail with main's tool-loop.ts/provider-hints.ts. A couple of things before merge:

  1. In-place edit of a non-text part not detected (still 400s). tool-loop.ts:795-803 snapshots only text strings by value, other parts by reference. Probe: prepareMessages setting messages[2].content[0].output = {...} on a tool-result before turn 3 -> request 3 sends the edited tool_result and still replays sig-2 after it. docs/executors.mdx:73 promises "in place or by replacement". Fix: snapshot tool-result output by value too, or narrow docs/JSDoc to "replace, don't mutate". The MessageShape comment has the same overclaim.
  2. False positive when prepareMessages deep-clones each turn (tool-loop.ts:694-699). With structuredClone, replayed thinking per request was [[], [sig-1], [], []]: all thinking dropped from turn 3, "rewritten" logged every turn, prompt cache misses from message 1. Same fix as 1 (value compare), or document "return the same objects for messages you didn't change".
  3. Bedrock application inference profile ARNs (provider-hints.ts:95): arn:...:application-inference-profile/<id> hides the model id, so 5.x through it still 400s. Cross-region/system profiles match. At least note the limit.
  4. shapeOf / sameShape missing JSDoc.
  5. Changeset explains internals. One sentence on the symptom fixed is enough.

This was an automated review.

The rewrite check kept text strings by value and every other part by reference, so an executor editing a tool result below the part slipped through, and one cloning the history each turn was flagged every turn. Message shapes are now serialized without provider options, with binary payloads compared by bytes. Notes the Bedrock application inference profile limit and shortens the changeset.
@TheTuso

TheTuso commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

[claude-fable-5-1] responding on behalf of TheTuso

All five taken, in c304977:

  1. and 2. The rewrite check now compares what each message holds by value: content serialized with providerOptions and providerMetadata left out, binary payloads compared by bytes. An edit below a part shows up; a cloned history does not. Two unit cases cover both (tool-loop-prefix.test.ts).
  2. The ARN limit is noted in speaksAnthropic's JSDoc. It is not fixable from the id, and the docs never describe that setup, so no docs line.
  3. JSDoc on the shape helpers.
  4. Changeset cut to one sentence on the symptom.

Verified on the built tree: Opus 5.5 through the gateway with the binding check enforced, all six testbed agent files, 29 of 29, same call count and cache rate as before the change, five rewrite notes at five distinct elision points.

@okwasniewski

Copy link
Copy Markdown
Member

Automated review (Claude Code on behalf of @okwasniewski)

Re-checked c304977. All five asks are done:

  • Messages are compared by value, with provider options left out.
  • structuredClone no longer causes a false positive.
  • The Bedrock app-profile ARN limit is documented.
  • JSDoc and changeset trims are in.

Typecheck is clean and all unit tests pass. A six-request run with an early elision showed one note and kept later thinking. Live gateway calls on Sonnet 4.5 (thinking on) and Opus 5.5 accept requests with the thinking dropped. CI is green. Ready for human review.

Leftovers:

  1. Bytes edited in place are missed (confirmed with a test). shapeOf (around tool-loop.ts:805) keeps the same Uint8Array objects, and sameBytes returns true right away when both sides are the same object. So after editing message 0's image bytes in place, sig-1/sig-2 were still sent. That contradicts "binary payloads compared by bytes" in docs/executors.mdx. Fix: copy or hash the bytes when taking the snapshot.
  2. tool-loop.ts is now 994 lines. The roughly 80 lines of message comparison (MessageShape, shapeOf, sameShape, sameBytes) stand alone, so they could move to their own module, and that's the natural place for fix 1. lastRequest and lastShape hold the same request twice.
  3. The tool-calls-only rule at tool-loop.ts:712 is now in every model's system prompt on every step, not just after the switch to auto. It's only verified on Opus 5.5. A web-bench run on a non-Anthropic default model would be worth it.
  4. Nits: lastShape JSDoc (tool-loop.ts:242) still says "by reference", and sameBytes has no JSDoc.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants