Skip to content

[fix] Stop creating the chat bootstrap row from inside an atom read - #5920

Closed
moataz-hjaiji wants to merge 1 commit into
Agenta-AI:mainfrom
moataz-hjaiji:fix/5344-jotai-store-mutation-during-read
Closed

[fix] Stop creating the chat bootstrap row from inside an atom read#5920
moataz-hjaiji wants to merge 1 commit into
Agenta-AI:mainfrom
moataz-hjaiji:fix/5344-jotai-store-mutation-during-read

Conversation

@moataz-hjaiji

Copy link
Copy Markdown
Contributor

Summary

Fixes #5344Detected store mutation during atom read in the agent playground.

Thanks @ardaerzin for walking the graph on the issue; this follows the shape you laid out there, including the host correction.

Root cause. generationRowIdsAtom answered "which rows exist" and created chat mode's blank first user message when there were none, writing to the store from inside its own read:

if (rowIds.length === 0) {
    get(playgroundStoreAtom).set(addUserMessageAtom, {loadableId, userMessage: null})
    return get(sharedMessageIdsAtomFamily(loadableId))
}

It isn't only a console warning. cancelTestsMutationAtom reads the row ids from inside a write (execution/generationSelectors.ts:207), so cancelling on an empty chat could re-entrantly create the blank row mid-write. The "once per onboarding page load, silent everywhere else" pattern is a mount-timing artifact — the trailing get on line 971 clears jotai's dev flag when that atom recomputes and doesn't when the set's own flush already did. The impurity is present on every chat surface; it is just silent on most of them.

Fix.

  1. generationRowIdsAtom's read is now pure — chat mode returns sharedMessageIdsAtomFamily(loadableId), nothing else.
  2. needsChatBootstrapRowAtom (pure) answers the condition; ensureChatBootstrapRowAtom performs the write and re-checks every condition inside the write, so repeated callers in one tick still yield exactly one row.
  3. A null-rendering <ChatRowBootstrap /> child of MainLayout drives it from an effect.

Why MainLayout and not ExecutionItems. Comparison-mode chat never goes through ExecutionItems — the layout renders GenerationComparisonRenderer, and GenerationComparisonChatOutput reads generationRowIds directly (ExecutionItemComparisonView/GenerationComparisonChatOutput/index.tsx:46). Hosting the effect in ExecutionItems would have silently cost comparison chat its blank first turn, and ExecutionItems also mounts once per column. MainLayout is the single shared entry for every playground surface, so one instance covers single + comparison exactly once. It is a separate child so the row-count subscription re-renders a null component rather than the whole layout.

Agents are excluded (isChat && !isAgent, root entity via isAgentModeAtomFamily). Agents carry is_chatcreateEphemeralAppFromTemplate seeds is_chat: type === "chat" || type === "agent" — so the chat branch ran for them, but the agent surface renders AgentGenerationPanel and never reads sharedMessageIdsAtomFamily. The row was write-only cost there. Nothing outside execution/selectors.ts reads that family, so this removes work rather than changing behaviour anyone observes.

No new dependency — jotai-effect is not in the tree and this does not add it.

Testing

Verified locally

pnpm vitest run in packages/agenta-playground: 17 test files, 221 tests passed. tsc --noEmit clean for both the package and web/oss. eslint and prettier clean on the changed src files.

Not verified in a browser by me — worth confirming on a fresh onboarding load that the warning is gone and the blank composer row still appears in single and comparison chat.

Added or updated tests

packages/agenta-playground/tests/unit/chatBootstrapRow.test.ts — 6 tests, the directory had no coverage for the bootstrap:

  • reading generationRowIdsAtom never creates a row (the regression itself)
  • ensureChatBootstrapRowAtom creates exactly one row for an empty chat playground
  • idempotency — three calls still yield one row
  • agent-mode no-op
  • completion-mode no-op, and no-op while the playground has no node

One detail worth flagging for review: the tests set playgroundStoreAtom to the test store in beforeEach. My first version did not, and the purity test passed with the bug re-introducedplaygroundStoreAtom defaults to jotai's global store, so the impure write landed there while the assertions read an isolated createStore(). With the wiring in place, re-introducing the old read fails it correctly:

× does not create a row when read on an empty chat playground
  AssertionError: expected [ Array(1) ] to deeply equal []

The comment in beforeEach records why that line is load-bearing.

QA follow-up

  • Confirm the blank first turn still appears in comparison chat (two chat revisions side by side) — that is the legacy path this PR deliberately moved the host to cover.
  • Confirm a chat app whose entity is still loading (isChatMode === undefined) settles into a blank row once the flags arrive.
  • Confirm the drawer surfaces that mount MainLayout (WorkflowRevisionDrawerWrapper, ConfigureEvaluator, CreateEvaluatorDrawer) still get the row.
  • Cancel-on-empty-chat: previously this could create the row re-entrantly mid-write via cancelTestsMutationAtom; worth a manual pass.

Demo

N/A — no visible UI change. The blank first row behaves exactly as before; the fix is where it is created from.

Checklist

  • I have included a video or screen recording for UI changes, or marked Demo as N/A
  • Relevant tests pass locally
  • Relevant linting and formatting pass locally
  • I have signed the CLA, or I will sign it when the bot prompts me

`generationRowIdsAtom` answered "which rows exist" AND created the blank
first user message when there were none, writing to the store from inside
its own read. Jotai flags that (`Detected store mutation during atom read`),
but it is worse than a warning: `cancelTestsMutationAtom` reads the row ids
from inside a write, so cancelling on an empty chat could re-entrantly
create the row mid-write.

The read is now pure. `needsChatBootstrapRowAtom` answers the condition and
`ensureChatBootstrapRowAtom` performs the write, re-checking every condition
inside the write so repeated callers still yield exactly one row.

Driven from a null-rendering `ChatRowBootstrap` child of `MainLayout` rather
than `ExecutionItems`: comparison-mode chat never goes through that
component — the layout renders `GenerationComparisonRenderer`, which reads
the row ids directly — so hosting it there would silently cost comparison
chat its blank first turn. `MainLayout` is the single shared entry for every
playground surface, so one instance covers both views exactly once.

Agents are excluded. They carry `is_chat` too, so the chat branch ran for
them, but the agent surface renders `AgentGenerationPanel` and never reads
`sharedMessageIdsAtomFamily` — the row was write-only cost there.

Fixes Agenta-AI#5344
Copilot AI lite review requested due to automatic review settings August 10, 2026 22:30
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

@moataz-hjaiji is attempting to deploy a commit to the agenta projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi @moataz-hjaiji, thanks for opening a pull request. 🙏

This PR was automatically closed because it does not yet meet our contribution requirements:

  • This PR changes functional code (SDK, API, or frontend) but includes no demo. Add a screenshot or short video of the change. Only test-only, docs-only, or chore changes may skip it.

We ask for this so every change is documented and demonstrably tested before review.

How to get it reopened
Update the PR description (and add a demo recording if your change touches functional code). The bot reopens the PR automatically once the requirements are met. No need to open a new one.

See the Contributing guide and Creating your first PR. If you think this was closed in error, leave a comment and a maintainer will take a look.

@github-actions github-actions Bot added the incomplete-pr PR is missing required template sections or a demo recording label Aug 10, 2026
@dosubot dosubot Bot added bug report Something isn't working frontend tests labels Aug 10, 2026
@github-actions github-actions Bot closed this Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da84c719-6999-48c8-8271-dfa8e2f0c06d

📥 Commits

Reviewing files that changed from the base of the PR and between 3db504c and 1f90f7e.

📒 Files selected for processing (6)
  • web/oss/src/components/Playground/Components/MainLayout/index.tsx
  • web/packages/agenta-playground/src/index.ts
  • web/packages/agenta-playground/src/state/execution/index.ts
  • web/packages/agenta-playground/src/state/execution/selectors.ts
  • web/packages/agenta-playground/src/state/index.ts
  • web/packages/agenta-playground/tests/unit/chatBootstrapRow.test.ts

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Chat playgrounds now automatically display an initial blank chat row when needed.
    • Blank-row setup works consistently across single and comparison playground views.
    • The setup is safe to repeat and does not create duplicate rows.
    • Agent and completion-based playgrounds remain unchanged.
  • Bug Fixes

    • Improved chat initialization to prevent missing first-message rows.

Walkthrough

The playground now detects missing initial chat rows without mutating during reads. MainLayout invokes an idempotent bootstrap action, which creates a blank user message only for supported chat entities.

Changes

Chat row bootstrap

Layer / File(s) Summary
Bootstrap state and mutation
web/packages/agenta-playground/src/state/execution/selectors.ts
generationRowIdsAtom now performs pure reads. New selectors detect missing chat rows and create them idempotently for supported entities.
Public bootstrap exports
web/packages/agenta-playground/src/state/execution/index.ts, web/packages/agenta-playground/src/state/index.ts, web/packages/agenta-playground/src/index.ts
The bootstrap selectors and mutation are exported through the playground package APIs.
Layout integration and validation
web/oss/src/components/Playground/Components/MainLayout/index.tsx, web/packages/agenta-playground/tests/unit/chatBootstrapRow.test.ts
MainLayout runs the bootstrap action. Tests cover read purity, idempotency, unsupported entities, and missing playground nodes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MainLayout
  participant needsChatBootstrapRowAtom
  participant ensureChatBootstrapRowAtom
  participant PlaygroundStore
  MainLayout->>needsChatBootstrapRowAtom: read bootstrap condition
  needsChatBootstrapRowAtom-->>MainLayout: report whether a row is needed
  MainLayout->>ensureChatBootstrapRowAtom: request row creation
  ensureChatBootstrapRowAtom->>PlaygroundStore: recheck and add blank user message
Loading

Possibly related PRs

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

bug report Something isn't working frontend incomplete-pr PR is missing required template sections or a demo recording size:M This PR changes 30-99 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(bug) Jotai "store mutation during atom read" warning in the agent playground

2 participants