{"record":{"id":"f1768dd918d818f8","repo":"moeru-ai/airi","slug":"id-is-required-to-remove-a-widget","errorCode":null,"errorMessage":"id is required to remove a widget.","messagePattern":"id is required to remove a widget\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.ts","lineNumber":291,"sourceCode":"    case 'update': {\n      if (!normalizedId)\n        throw new Error('id is required to update a widget.')\n\n      const componentProps = normalizeComponentProps(input.componentProps)\n      const sanitizedComponentProps = sanitizeComponentPropsForDispatch(input.componentName, componentProps)\n      const windowSize = resolveWindowSize(input.componentName, sanitizedComponentProps, input.windowSize)\n      await invokers.updateWidget({\n        id: normalizedId,\n        componentProps: sanitizedComponentProps,\n        ...(input.alwaysOnTop === undefined ? {} : { alwaysOnTop: input.alwaysOnTop }),\n        ...(windowSize === undefined ? {} : { windowSize }),\n      })\n\n      return `Updated widget (${normalizedId}).`\n    }\n    case 'remove': {\n      if (!normalizedId)\n        throw new Error('id is required to remove a widget.')\n\n      await invokers.removeWidget({ id: normalizedId })\n      return `Removed widget (${normalizedId}).`\n    }\n    case 'clear': {\n      await invokers.clearWidgets()\n      return 'Cleared all widgets.'\n    }\n    case 'open': {\n      const id = await invokers.prepareWindow(normalizedId ? { id: normalizedId } : {})\n      await invokers.openWindow(normalizedId ? { id: normalizedId } : {})\n      return `Opened widget window${id ? ` (${id})` : ''}.`\n    }\n    default:\n      return 'No action performed.'\n  }\n}\n","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.ts#L273-L309","documentation":"Thrown by executeWidgetAction when action='remove' but no widget id can be resolved. The guard at widgets.ts:291 mirrors the update guard: normalizedId (trimmed id) must be truthy because removeWidget IPC targets exactly one widget by id. Unlike 'clear', 'remove' cannot operate without an identifier.","triggerScenarios":"Calling executeWidgetAction({ action: 'remove' }) with id omitted, empty, or whitespace. An LLM tool-call selecting remove but failing to carry the widget id forward.","commonSituations":"Confusing 'remove' (single widget by id) with 'clear' (all widgets) and omitting id; losing the id between spawn and remove turns in an agent conversation.","solutions":["Pass the specific widget id to remove.","If the intent is to remove all widgets, use action: 'clear' instead, which takes no id.","Track spawned widget ids in caller state so remove always has a concrete target."],"exampleFix":"// before\nawait executeWidgetAction({ action: 'remove', id: '' })\n// after\nawait executeWidgetAction({ action: 'remove', id: 'w1' })","handlingStrategy":"validation","validationCode":"const normalizedId = input.id?.trim()\nif (input.action === 'remove' && !normalizedId)\n  throw new Error('id is required to remove a widget.')\n// if intending to remove all, use action: 'clear'","typeGuard":"function hasRemoveId(input) {\n  return input.action !== 'remove' || Boolean(input.id?.trim())\n}","tryCatchPattern":"try {\n  await executeWidgetAction(input)\n} catch (e) {\n  if (e.message.includes('id is required to remove'))\n    // either supply the id or switch to action: 'clear'\n  throw e\n}","preventionTips":["Distinguish single-widget remove from clear-all at the call site.","Track live widget ids so remove always has a valid target."],"tags":["widget","validation","electron","stage-tamagotchi","ipc"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}