fix(extension): resolve React re-render disposal race condition when executing tasks via MCP - #587
fix(extension): resolve React re-render disposal race condition when executing tasks via MCP#587SparshGarg999 wants to merge 1 commit into
Conversation
…executing tasks via MCP
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: abf21c132f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const initialConfig = { ...llmConfig, ...advancedConfig, language } | ||
| setConfig(initialConfig) | ||
| setupAgent(initialConfig) |
There was a problem hiding this comment.
Guard the late initializer before rebuilding the agent
If the Hub receives an execute message with incomingConfig before this initial chrome.storage.local.get resolves, configure() now creates an agent and execute() can start a task, but this pending initializer will still call setupAgent(initialConfig) afterwards. Because setupAgent first runs cleanupRef.current(), the late initializer disposes the active MultiPageAgent and aborts the just-started run, so fast MCP callers can still get the immediate Task aborted failure this patch is meant to remove. Skip the initial setup when an agent/config was already installed, or cancel the async initializer after a later configure.
Useful? React with 👍 / 👎.
Brings in: - alibaba#587 React re-render disposal race fix (useAgent.ts) - heartbeat polling context-invalidation guard (RemotePageController.content.ts) - .gitignore additions for local tool caches
BUKOWSKIREAL
left a comment
There was a problem hiding this comment.
I independently hit and debugged this exact bug (#570) before finding this PR,
so I'd like to add a verification data point.
Reproducing the bug: I rebuilt the relevant logic of useAgent.ts /
useHubWs / PageAgentCore's abort handling in a minimal jsdom repro running
react@19.2.7 (same version as the extension), with no mocked scheduling. On
current main's logic, the failure is deterministic: setConfig at the end of
configure() schedules the re-render through React's scheduler (a macrotask),
while the await configure(...) continuation in onExecute is a microtask —
so execute(task) always starts on the old agent, which the effect cleanup
then disposes, aborting the task. This also explains the 100% failure rate:
configure() creates a new config object reference on every call, so the
agent was torn down even when the config values were identical.
(Full repro timeline posted in #570.)
Verifying this PR's approach: I ported this PR's setupAgent design into
the same repro. The ordering flips as intended — dispose + recreate now happen
synchronously inside configure(), so by the time execute(task) runs,
agentRef.current is already the new agent:
1. hub onExecute: await configure(...)
2. agent#1 dispose() called
3. agent#2 created
4. hub onExecute: configure() resolved -> execute(task)
5. agent#2 execute() STARTED
6. agent#2 task COMPLETED normally
One thought: even with this fix, the agent is still disposed and recreated
on every MCP task (the MCP server attaches the same config to each execute,
and configure() always produces a new object). That's now harmless for
correctness, but skipping the rebuild when the incoming config is deep-equal
to the current one could be a cheap follow-up — either here or in
useHubWs.onExecute.
Thanks for fixing this — it makes the MCP path actually usable.
|
Thanks @BUKOWSKIREAL for taking the time to reproduce and validate this independently. I'm glad your repro confirmed the root cause and that the new lifecycle behaves as intended. I also agree that avoiding unnecessary agent recreation when the config hasn't changed would be a worthwhile optimization, though I think it's best handled as a follow-up since it isn't required for correctness. |
This PR resolves the instant "Task aborted" error when executing tasks via MCP (Issue #570):
Root Cause
Every time
execute_taskis triggered, the MCP client sends updated configuration (likeLLM_API_KEY) to the Hub. This updates the local storage, triggering a React state update (setConfig) inuseAgent.ts, causing a re-render. Because theconfigobject changes, theuseEffectcleanup function—which callsagent.dispose()—is triggered.agent.dispose()executesabortController.abort(), instantly killing the task that was just initiated in the same tick.Fix
Refactored
useAgent.tsto manage the creation and disposal of theMultiPageAgentmanually via asetupAgentcallback, instead of reactively viauseEffect([config]). We only recreate the agent when new configuration is set, avoiding any Reactive cleanup/dispose race condition while a task is running.