{"record":{"id":"69734940e99d0d3a","repo":"aaif-goose/goose","slug":"cannot-update-message-while-prompt-is-active","errorCode":null,"errorMessage":"Cannot update message while prompt is active","messagePattern":"Cannot update message while prompt is active","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/desktop/src/acp/chatSessionController.ts","lineNumber":286,"sourceCode":"    editSnapshot?.chatState === ChatState.WaitingForUserInput &&\n    getPendingToolConfirmationIds(editSnapshot?.messages ?? []).size > 0;\n  const isIdle = editSnapshot?.chatState === ChatState.Idle;\n  const pendingToolPermissionPromptAttemptId = isPendingToolPermission\n    ? activePromptAttemptId\n    : undefined;\n  const canEditInPlace = isIdle || pendingToolPermissionPromptAttemptId != null;\n\n  if (!canEditInPlace) {\n    return;\n  }\n\n  if (pendingToolPermissionPromptAttemptId != null) {\n    const cancellation = acpChatSessionActions.startPromptCancellation(\n      sessionId,\n      pendingToolPermissionPromptAttemptId\n    );\n    if (!cancellation) {\n      throw new Error('Cannot update message while prompt is active');\n    }\n\n    const promptCancellationSettled = acpChatSessionActions.waitForPromptCancellation(\n      sessionId,\n      pendingToolPermissionPromptAttemptId\n    );\n\n    try {\n      await acpCancelPrompt(sessionId);\n    } catch {\n      acpChatSessionActions.restorePromptCancellation(\n        sessionId,\n        pendingToolPermissionPromptAttemptId\n      );\n      throw new Error('Cannot update message because the active prompt could not be cancelled');\n    }\n\n    cancelAcpPermissionRequestsForSession(sessionId);","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/ui/desktop/src/acp/chatSessionController.ts#L268-L304","documentation":"Thrown when editing a message that has a pending tool-permission prompt: the code calls acpChatSessionActions.startPromptCancellation(sessionId, attemptId) and expects a cancellation handle back. A null return means the store could not start a cancellation for that prompt attempt — typically because the prompt attempt state changed between reading the snapshot and acting on it (it settled, was restored, or never existed). The edit aborts rather than corrupting prompt state.","triggerScenarios":"Reading pendingToolPermissionPromptAttemptId from a snapshot, then the permission prompt is answered or cancelled concurrently (user clicks allow/deny in another surface), so startPromptCancellation finds nothing to cancel; stale attempt id after restorePromptCancellation; double-submit of the same edit.","commonSituations":"User answers a permission dialog at the same moment they submit a message edit; two edit attempts racing; automated tests replaying an edit after the prompt already resolved.","solutions":["Re-read the snapshot and retry the edit once — after the race resolves, either the prompt is gone (isIdle path) or the attempt id is current.","Disable the edit affordance while a permission prompt is being answered elsewhere.","Make the edit submit path idempotent so a second attempt after this error succeeds."],"exampleFix":"// before\nconst cancellation = acpChatSessionActions.startPromptCancellation(sessionId, pendingToolPermissionPromptAttemptId);\nif (!cancellation) {\n  throw new Error('Cannot update message while prompt is active');\n}\n\n// after (retry once against fresh state, then surface a soft failure)\nlet cancellation = acpChatSessionActions.startPromptCancellation(sessionId, pendingToolPermissionPromptAttemptId);\nif (!cancellation) {\n  await nextStoreUpdate(sessionId); // wait one store tick\n  const fresh = acpChatSessionStore.getSnapshot(sessionId);\n  if (fresh?.chatState === ChatState.Idle) return submitEdit(sessionId, messageId, /* ... */);\n  throw new Error('Cannot update message while prompt is active');\n}","handlingStrategy":"validation","validationCode":"// Re-check chat state right before submitting the edit\nconst snapshot = options.getCurrentSnapshot();\nconst stillPending =\n  snapshot?.chatState === ChatState.WaitingForUserInput &&\n  getPendingToolConfirmationIds(snapshot?.messages ?? []).size > 0;\nif (!stillPending && snapshot?.chatState !== ChatState.Idle) {\n  return; // prompt lifecycle changed; retry the interaction instead\n}","typeGuard":null,"tryCatchPattern":"try {\n  await submitEditedMessage(sessionId, messageId, newContent, retainedImages, options);\n} catch (error) {\n  if (/Cannot update message while prompt is active/.test(String(error))) {\n    await nextStoreTick();\n    return submitEditedMessage(sessionId, messageId, newContent, retainedImages, options); // one retry\n  }\n  throw error;\n}","preventionTips":["Serialize permission-dialog answers and message edits through one queue per session.","Read pendingToolPermissionPromptAttemptId from the freshest snapshot at the last possible moment.","Disable the edit button while a permission prompt is resolving."],"tags":["race-condition","prompt-cancellation","chat","acp","state"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}