moeru-ai/airi · error · Error

id is required to update a widget.

Error message

id is required to update a widget.

What it means

Thrown by executeWidgetAction when action='update' but no widget id can be resolved. normalizedId is computed as input.id?.trim() || undefined, so an empty or whitespace id becomes undefined and fails the guard at widgets.ts:275. The update action must target an existing widget by id, since updateWidget IPC requires the id to locate the live widget.

Source

Thrown at apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.ts:275

      const componentProps = normalizeComponentProps(input.componentProps)
      const sanitizedComponentProps = sanitizeComponentPropsForDispatch(input.componentName, componentProps)
      const windowSize = resolveWindowSize(input.componentName, sanitizedComponentProps, input.windowSize)
      const ttlMs = input.ttlSeconds ? Math.floor(input.ttlSeconds * 1000) : 0
      const id = await invokers.addWidget({
        id: normalizedId,
        componentName: input.componentName,
        componentProps: sanitizedComponentProps,
        size: input.size ?? 'm',
        ...(input.alwaysOnTop === undefined ? {} : { alwaysOnTop: input.alwaysOnTop }),
        ...(windowSize === undefined ? {} : { windowSize }),
        ttlMs,
      })

      return `Spawned widget${id ? ` (${id})` : ''}.`
    }
    case 'update': {
      if (!normalizedId)
        throw new Error('id is required to update a widget.')

      const componentProps = normalizeComponentProps(input.componentProps)
      const sanitizedComponentProps = sanitizeComponentPropsForDispatch(input.componentName, componentProps)
      const windowSize = resolveWindowSize(input.componentName, sanitizedComponentProps, input.windowSize)
      await invokers.updateWidget({
        id: normalizedId,
        componentProps: sanitizedComponentProps,
        ...(input.alwaysOnTop === undefined ? {} : { alwaysOnTop: input.alwaysOnTop }),
        ...(windowSize === undefined ? {} : { windowSize }),
      })

      return `Updated widget (${normalizedId}).`
    }
    case 'remove': {
      if (!normalizedId)
        throw new Error('id is required to remove a widget.')

      await invokers.removeWidget({ id: normalizedId })

View on GitHub (pinned to 27111382b4)

Solutions

  1. Pass the id returned by the prior spawn call when issuing an update.
  2. If you only know componentName, spawn a new widget instead, or track the id from the spawn result string 'Spawned widget (ID).'
  3. For LLM-driven flows, include the spawned widget id in the conversation context so the model can echo it on update.

Example fix

// before
await executeWidgetAction({ action: 'update', id: '  ', componentProps: { city: 'Osaka' } })
// after
await executeWidgetAction({ action: 'update', id: 'w1', componentProps: { city: 'Osaka' } })
Defensive patterns

Strategy: validation

Validate before calling

const normalizedId = input.id?.trim()
if (input.action === 'update' && !normalizedId)
  throw new Error('id is required to update a widget.')

Type guard

function hasUpdateId(input) {
  return input.action !== 'update' || Boolean(input.id?.trim())
}

Try / catch

try {
  await executeWidgetAction(input)
} catch (e) {
  if (e.message.includes('id is required to update'))
    // re-fetch widget id from prior spawn result
  throw e
}

Prevention

When it happens

Trigger: Calling executeWidgetAction({ action: 'update' }) with id omitted, empty, or whitespace. Passing the stage_widgets tool an update turn where the model left id blank because it assumed update-by-componentName.

Common situations: An LLM confuses update (needs id) with spawn (id optional) and sends update without the widget id returned from the prior spawn; a caller reuses a spawn payload object for an update but drops the id field.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/fe2d848c31069629. Report an issue: GitHub.