{"record":{"id":"1487947bb2bce2f5","repo":"alibaba/page-agent","slug":"agent-is-already-running-a-task","errorCode":null,"errorMessage":"Agent is already running a task.","messagePattern":"Agent is already running a task\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/mcp/src/hub-bridge.js","lineNumber":86,"sourceCode":"\t\t})\n\t}\n\n\tget connected() {\n\t\treturn this.#hub?.readyState === 1\n\t}\n\n\tget busy() {\n\t\treturn this.#pendingTask !== null\n\t}\n\n\t/**\n\t * @param {string} task\n\t * @param {Record<string, unknown>} [config]\n\t * @returns {Promise<{success: boolean, data: string}>}\n\t */\n\tasync executeTask(task, config) {\n\t\tif (!this.connected) throw new Error('Hub is not connected. Is the extension running?')\n\t\tif (this.#pendingTask) throw new Error('Agent is already running a task.')\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.#pendingTask = { resolve, reject }\n\t\t\tthis.#hub.send(JSON.stringify({ type: 'execute', task, config }))\n\t\t})\n\t}\n\n\tstopTask() {\n\t\tif (this.connected) {\n\t\t\tthis.#hub.send(JSON.stringify({ type: 'stop' }))\n\t\t}\n\t}\n\n\t// TODO: Add version checking\n\n\t/** @param {import('ws').WebSocket} ws */\n\t#onConnection(ws) {\n\t\tif (this.#hub && this.#hub.readyState === 1) {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/alibaba/page-agent/blob/d02db1ee7c41f5315beda88bab2fe935c580f662/packages/mcp/src/hub-bridge.js#L68-L104","documentation":"HubBridge.executeTask refuses to start a second concurrent task: if #pendingTask is set (a previous executeTask promise is still unresolved), it throws this error. The bridge supports one in-flight task at a time.","triggerScenarios":"Calling executeTask() again while a previous task is still awaiting its result — e.g. concurrent MCP tool invocations, a client timeout that abandons the promise without the bridge clearing it, or firing two requests in parallel.","commonSituations":"MCP client sending parallel tool calls; aggressive client-side timeouts that give up on a slow task but leave it pending in the bridge; missing await causing accidental double invocation; long-running automation tasks overlapping.","solutions":["Serialize task execution: await the previous executeTask promise (or a queue) before issuing the next","If a task was abandoned by a client timeout, disconnect/reconnect the bridge to reset state, or add a cancel mechanism if available","On the client side, set timeouts generous enough for page automation tasks to finish","Guard with a check of the bridge's busy state before dispatching"],"exampleFix":"// before\nconst [a, b] = await Promise.all([\n  bridge.executeTask(task1),\n  bridge.executeTask(task2), // throws 'already running'\n])\n\n// after\nconst a = await bridge.executeTask(task1)\nconst b = await bridge.executeTask(task2) // run sequentially","handlingStrategy":"validation","validationCode":"let chain: Promise<unknown> = Promise.resolve()\nfunction runTask(task: string) {\n  const next = chain.then(() => bridge.executeTask(task))\n  chain = next.catch(() => {}) // keep queue alive on failures\n  return next\n}","typeGuard":"null","tryCatchPattern":"try {\n  await bridge.executeTask(task)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('already running')) {\n    await delay(1000) // or await the in-flight promise\n    return bridge.executeTask(task)\n  }\n  throw e\n}","preventionTips":["Serialize executeTask calls through a queue; never fire them concurrently","Await each task fully before starting the next","Set client timeouts longer than the worst-case automation task"],"tags":["concurrency","mcp","extension","task-queue"],"backgroundTag":"concurrent-request-conflict","analyzedSha":"d02db1ee7c41f5315beda88bab2fe935c580f662","analyzedAt":"2026-08-28T19:39:48.634Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}