{"record":{"id":"67ee7108be9d85b4","repo":"homebridge/homebridge","slug":"status-failure-67ee71","errorCode":"Status.Failure","errorMessage":"Failed to change system mode: ${message}","messagePattern":"Failed to change system mode: (.+?)","errorType":"exception","errorClass":"StatusResponseError","httpStatus":null,"severity":"error","filePath":"src/matter/behaviors/ThermostatBehavior.ts","lineNumber":71,"sourceCode":"        endpointId,\n        'thermostat',\n        'systemModeChange',\n        { systemMode: value, oldSystemMode: oldValue },\n      )\n\n      // Sync state to cache\n      registry.syncStateToCache(endpointId, 'thermostat', { systemMode: value })\n    } catch (error) {\n      // If user handler already threw a StatusResponseError, propagate it as-is\n      // This sends a proper Matter protocol error response to the controller\n      if (MatterStatus.isMatterProtocolError(error)) {\n        throw error\n      }\n\n      // For other errors, wrap in appropriate StatusResponseError\n      // This prevents the endpoint from crashing and keeps the device online\n      const message = error instanceof Error ? error.message : String(error)\n      throw new StatusResponseError(`Failed to change system mode: ${message}`, Status.Failure)\n    }\n  }\n\n  async #handleOccupiedHeatingSetpointChanging(value: unknown): Promise<void> {\n    const endpointId = this.endpoint.id\n    const registry = this.getRegistry()\n    // Using 'as any' because occupiedHeatingSetpoint is feature-dependent (Heating feature)\n    const oldValue = (this.state as any).occupiedHeatingSetpoint\n\n    try {\n      // Execute user handler\n      await registry.executeHandler(\n        endpointId,\n        'thermostat',\n        'occupiedHeatingSetpointChange',\n        { occupiedHeatingSetpoint: value as number, oldOccupiedHeatingSetpoint: oldValue },\n      )\n","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/homebridge/homebridge/blob/edf54930340d67cade23d01abd41b03cd9621e8b/src/matter/behaviors/ThermostatBehavior.ts#L53-L89","documentation":"When the thermostat's systemMode attribute changes (off/heat/cool/auto), the behavior emits a `systemModeChange` event to the plugin-registered handler. If that handler throws any non-Matter error, it is wrapped as `StatusResponseError('Failed to change system mode: <message>', Status.Failure)` via the reactTo event path. The Matter attribute transaction aborts with Failure instead of crashing the endpoint.","triggerScenarios":"A controller sets the thermostat mode in Home and the plugin's `thermostat.systemModeChange` handler rejects — e.g. the HVAC API refuses 'cool' when only heating is wired, an auth token expired, or the handler throws during a write to the physical thermostat.","commonSituations":"Plugins backing thermostats with partial mode support receiving a mode the hardware cannot do; cloud HVAC API downtime; mode changes made simultaneously from two controllers racing in the plugin's state.","solutions":["Fix the plugin error reported after 'Failed to change system mode: '.","Advertise only the modes the hardware supports (Thermostat cluster mode feature flags) so controllers never request unsupported modes.","Throw StatusResponseError/InvalidCommand from the plugin for unsupported mode transitions.","Check HVAC cloud credentials/connectivity in the plugin when the failure is intermittent.","Serialize mode-change handling in the plugin to avoid races between controllers."],"exampleFix":"// before\nsystemModeChange({ systemMode }) { return this.hvac.setMode(systemMode) } // throws on 'cool'\n// after\nsystemModeChange({ systemMode }) {\n  if (!this.supportedModes.has(systemMode)) {\n    throw new StatusResponseError('mode not supported by hvac', Status.InvalidCommand)\n  }\n  return this.hvac.setMode(systemMode)\n}","handlingStrategy":"try-catch","validationCode":"// gate mode changes on actual hardware support\nconst SUPPORTED_SYSTEM_MODES = new Set([0, 4, 3, 1]) // off, cool, heat, auto per device\nif (!SUPPORTED_SYSTEM_MODES.has(requestedSystemMode)) {\n  throw new StatusResponseError('system mode not supported by hvac', Status.InvalidCommand)\n}\nif (!this.hvac.online) throw new StatusResponseError('hvac offline', Status.Failure)","typeGuard":"function isMatterProtocolError(e: unknown): e is StatusResponseError {\n  return e instanceof StatusResponseError\n    || (typeof e === 'object' && e !== null && 'status' in e && typeof (e as any).code === 'number')\n}","tryCatchPattern":"try {\n  await registry.executeHandler(endpointId, 'thermostat', 'systemModeChange', payload)\n} catch (e) {\n  logger.warn('system mode change failed:', e instanceof Error ? e.message : e)\n  if (isMatterProtocolError(e)) throw e\n  throw new StatusResponseError(`Failed to change system mode: ${String(e)}`, Status.Failure)\n}","preventionTips":["Advertise only system modes the HVAC hardware supports via cluster feature flags","Keep HVAC API credentials valid; refresh tokens proactively","Serialize mode writes to avoid races between multiple controllers","Log the original error inside the plugin — the wrapped message is your only breadcrumb"],"tags":["matter","thermostat","plugin-handler"],"backgroundTag":"matter-status-response-failure","analyzedSha":"edf54930340d67cade23d01abd41b03cd9621e8b","analyzedAt":"2026-08-30T21:28:53.235Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}