Error reference
Every failure Stewart reports has a reason. The exception class names the part of Stewart that failed, and
$e->reason names the failure: StoreError::TypeMismatch belongs to a StoreException. Words in braces are
filled in from $e->context. Handling errors explains how to catch them.
HaClientError
Section titled “HaClientError”Thrown as HaClientException.
| Reason | Message | What to do |
|---|---|---|
ClosedLocally |
The connection was closed locally while {phase}. | |
ConnectTimedOut |
Connecting to Home Assistant at {url} timed out after {timeout}. Check home_assistant.url. | |
ConnectFailed |
Could not connect to Home Assistant at {url}: {cause} | |
NotConnected |
Not connected to Home Assistant. | |
SendFailed |
Failed to send {command}: {cause} | |
ClosedDuringAuthentication |
Home Assistant closed the connection during authentication. | |
ConnectionDropped |
The connection to Home Assistant was lost ({detail}). | |
MessageTooLarge |
Home Assistant sent a message over the size limit, so the connection was dropped. Raise home_assistant.message_size_limit or home_assistant.frame_size_limit. | |
ProtocolViolation |
Home Assistant sent {detail}. | |
CommandTimedOut |
Home Assistant did not answer {command} within {timeout}. | |
CommandRejected |
{command} failed: {detail} | |
CommandUnauthorized |
{command} failed: {detail} | |
CommandUnencodable |
{command} could not be encoded: {cause} | |
UnexpectedGreeting |
Expected auth_required from Home Assistant, got “{receivedType}”. | |
TokenRejected |
Home Assistant rejected the access token: {reason}. Check STEWART_HOME_ASSISTANT__TOKEN. | |
AdministratorRequired |
Home Assistant refused the event subscription; the access token must belong to an administrator. Update STEWART_HOME_ASSISTANT__TOKEN. | Create the token from a Home Assistant administrator. |
EventBacklogExceeded |
Event handlers are more than {limit} events behind; dropping the connection. | |
EventQueueReentered |
An event callback cannot wait for the event queue it is being delivered by. | |
UrlInvalid |
“{url}” is not a Home Assistant URL; use http://, https://, ws:// or wss:// and a host. | |
ComponentInstanceInvalid |
“{instance}” is not an instance name; use lowercase letters, digits and underscores, starting with a letter, up to 64 characters. |
CodegenError
Section titled “CodegenError”Thrown as CodegenException.
| Reason | Message | What to do |
|---|---|---|
SnapshotUnreadable |
Snapshot {path} cannot be read. | |
SnapshotNotJson |
Snapshot {path} is not valid JSON: {reason} | |
NotASnapshot |
{path} is not a snapshot; write one with stewart generate --snapshot-out. |
|
OutputDirectoryNotCreatable |
Cannot create output directory {path}. | |
FileNotWritable |
Cannot write {path}. | |
FileNotDeletable |
Cannot delete {path}. | |
UnmarkedFileInTheWay |
{path} was not written by stewart generate; move it out of the output directory. | |
ShrinkRefused |
Generating would delete {deleted} of {existing} generated files; run make generate ARGS=--allow-shrink if that is intended. |
|
HomeAssistantNotRunning |
Home Assistant is {state}; run stewart generate again once it has started. |
CommandError
Section titled “CommandError”Thrown as CommandException.
| Reason | Message | What to do |
|---|---|---|
Rejected |
{reason} |
EventFireError
Section titled “EventFireError”Thrown as EventFireException.
| Reason | Message | What to do |
|---|---|---|
TypeInvalid |
An event type must be 1 to 64 characters, got “{eventType}”. | |
DataInvalid |
Data of event {eventType} at {path} is {actualType}; expected null, a finite scalar or an array of those. | |
DataNotKeyed |
Data of event {eventType} must be keyed by name, got a list. | |
Rejected |
Event {eventType} was rejected by Home Assistant: {detail} | |
Unreachable |
Event {eventType} could not reach Home Assistant: {detail} | |
TimedOut |
Event {eventType} timed out: {detail} | |
Overloaded |
Event {eventType} was refused: {detail} |
ExposureError
Section titled “ExposureError”Thrown as ExposureException.
| Reason | Message | What to do |
|---|---|---|
ComponentMissing |
Entity {key} cannot be exposed because the stewart Home Assistant integration is not installed; see stewart status. |
Install the Stewart integration in Home Assistant and restart it. |
ProtocolMismatch |
Entity {key} cannot be exposed because the stewart Home Assistant integration speaks another protocol; see stewart status. |
Update Stewart or the integration so both have the same version. |
ComponentRefused |
Entity {key} cannot be exposed because Home Assistant refused the stewart integration session; see stewart status. |
|
OutsideApp |
Entity {key} cannot be exposed outside an app, because its unique ID needs an app ID. | |
KeyInvalid |
Exposed entity key “{key}” is invalid; use up to {limit} lowercase letters, digits and “_”, starting with a letter. | |
KeyTaken |
App {appId} already exposes an entity with key {key}. | |
ConfigInvalid |
Home Assistant cannot use this entity configuration: {detail} | |
StateInvalid |
Home Assistant cannot use this entity state: {detail} | |
Removed |
Exposed entity {key} was removed; expose it again to use it. | |
Unreachable |
Could not reach the broker for exposed entity {key}: {detail} | |
TimedOut |
The broker did not answer for exposed entity {key} within {timeout}. |
HistoryError
Section titled “HistoryError”Thrown as HistoryException.
| Reason | Message | What to do |
|---|---|---|
WindowInvalid |
A history window must end after it starts, got {startsAt} to {endsAt}. | |
RecorderUnavailable |
History of {entityId} is unavailable because Home Assistant has no history integration loaded. | |
Rejected |
History of {entityId} was rejected by Home Assistant: {detail} | |
Unreachable |
History of {entityId} could not reach Home Assistant: {detail} | |
TimedOut |
History of {entityId} timed out: {detail} | |
Overloaded |
History of {entityId} was refused: {detail} |
IdentifierError
Section titled “IdentifierError”Thrown as IdentifierException.
| Reason | Message | What to do |
|---|---|---|
EntityIdInvalid |
Entity ID “{entityId}” is invalid; expected “ |
|
AppIdInvalid |
App ID “{appId}” is invalid; use lowercase letters, digits, “-” and “_”, starting with a letter. | |
AppIdTooLong |
App ID “{appId}” is longer than {limit} characters. | |
EntityNotGenerated |
Entity ID “{entityId}” is not a generated {domain} entity; run stewart generate if it is new. |
Fix the ID, or run make generate for a new entity. |
RegistryIdEmpty |
{kind} ID must not be empty. |
MqttError
Section titled “MqttError”Thrown as MqttException.
| Reason | Message | What to do |
|---|---|---|
TopicInvalid |
MQTT topic {topic} must be non-empty UTF-8, free of +, # and NUL characters. | |
PayloadUnencodable |
MQTT payload for {topic} cannot be encoded as JSON: {cause} | |
PayloadNotJson |
MQTT payload on {topic} is not valid JSON: {cause} | |
NotConfigured |
MQTT is not configured; set mqtt.url in stewart.yaml or STEWART_MQTT__URL in .env. | Set STEWART_MQTT__URL. |
PublishFailed |
Could not hand the MQTT message for {topic} to the broker: {cause} |
RegistryEditError
Section titled “RegistryEditError”Thrown as RegistryEditException.
| Reason | Message | What to do |
|---|---|---|
NotFound |
Entity {entityId} is not in the Home Assistant entity registry. | |
NothingToUpdate |
The registry update for {entityId} changes nothing. | |
Rejected |
Home Assistant rejected the registry update for {entityId}: {detail} | |
Unreachable |
The registry update for {entityId} could not reach Home Assistant: {detail} | |
TimedOut |
The registry update for {entityId} timed out: {detail} | |
Overloaded |
The registry update for {entityId} was refused: {detail} |
ScheduleError
Section titled “ScheduleError”Thrown as ScheduleException.
| Reason | Message | What to do |
|---|---|---|
CronInvalid |
Cron expression “{expression}” is invalid. | |
EntityTimeDomainUnsupported |
Entity “{entityId}” holds no time; expected an input_datetime or a sensor entity. | |
SunLocationUnknown |
A {event} schedule needs a location, and Home Assistant reported none. | Set a location in Home Assistant under Settings → System → General, then restart Stewart. |
TimeOfDayInvalid |
Time of day “{value}” is invalid; expected “HH:MM” or “HH:MM:SS”. | |
TimeOfDayOutOfRange |
Time of day {unit} {value} is outside 0-{highest}. | |
TimeWindowEmpty |
Time window starts and ends at {time}; start and end must differ. |
SelectorError
Section titled “SelectorError”Thrown as SelectorException.
| Reason | Message | What to do |
|---|---|---|
Empty |
{selectorKind} cannot be empty. | |
RegexInvalid |
Regular expression selector {pattern} is invalid: {compileError}. | |
MqttFilterInvalid |
MQTT topic filter {filter} must use + and # only as whole levels, with # last. |
ServiceCallError
Section titled “ServiceCallError”Thrown as ServiceCallException.
| Reason | Message | What to do |
|---|---|---|
Rejected |
{domain}.{service} was rejected by Home Assistant: {detail} | Try the same call under Developer tools → Actions in Home Assistant. |
Unreachable |
{domain}.{service} could not reach Home Assistant: {detail} | |
TimedOut |
{domain}.{service} timed out: {detail} | |
Overloaded |
{domain}.{service} was refused: {detail} | Call fewer services at once, for example one call with several targets. |
StateError
Section titled “StateError”Thrown as StateException.
| Reason | Message | What to do |
|---|---|---|
EntityNotFound |
Entity “{entityId}” does not exist in Home Assistant. | |
StateChangedViaEvents |
The “{eventType}” event is only delivered through watchStateChanges(), not watchEvents(). | |
ThresholdHysteresisNegative |
The hysteresis of {usage}() is {hysteresis}; it must be zero or more. | |
ThresholdNotFinite |
The threshold of {usage}() is {threshold}; it must be a finite number. | |
TransitionRuleConflict |
whenChangedTo() takes either from: or fromAnyState:, not both. |
StoreError
Section titled “StoreError”Thrown as StoreException.
| Reason | Message | What to do |
|---|---|---|
KeyInvalid |
Store key “{key}” is invalid; expected a letter or digit, then up to 127 letters, digits, “.”, “_”, “:” or “-”. | |
ValueNotEncodable |
The value for key “{key}” is not JSON-encodable: {cause} | |
ValueNotAMap |
{class}::toStorage() returned a list for key “{key}”; expected a map of named fields. | |
NotConfigured |
Persistence is not configured; set persistence.url in stewart.yaml or STEWART_PERSISTENCE__URL in .env. | Set STEWART_PERSISTENCE__URL. |
Unreachable |
The store at {target} is unreachable: {detail} | |
Refused |
The store at {target} refused the operation: {detail} | |
TimedOut |
The store at {target} did not answer within {timeout}. | |
RecentlyFailed |
The store at {target} failed moments ago and is not being retried yet: {detail} | |
TypeMismatch |
Key “{key}” holds {actual}, not {expected}. | Read the key with the type it was written with, or delete it. |
NotIncrementable |
Key “{key}” cannot be incremented: {detail} | |
ValueNotIncrementable |
The stored value cannot be incremented: {detail} | |
ValueMalformed |
Key “{key}” does not hold valid JSON: {cause} | |
ValueRejected |
{class}::fromStorage() rejected the value of key “{key}” (handle the old shape or delete the key): {cause} | Make fromStorage() accept the stored shape, or delete the key. |
SunError
Section titled “SunError”Thrown as SunException.
| Reason | Message | What to do |
|---|---|---|
CoordinateOutOfRange |
The {coordinate} {value} is outside -{limit} to {limit}. | |
LocationUnknown |
Home Assistant reported no location, so sun times are unknown. |
TimeError
Section titled “TimeError”Thrown as TimeException.
| Reason | Message | What to do |
|---|---|---|
DurationNegative |
Duration {microseconds}us is negative. | |
DurationNotFinite |
Duration {amount} is not a finite number. | |
DurationNotPositive |
The duration for {usage} must be at least 1ms. | |
DurationTooLarge |
Duration exceeds the 292-year maximum. | |
DurationUnparsable |
Duration “{text}” is invalid; expected a number and one unit, such as 500ms, 5s, 2m or 1h. | |
InstantUnparsable |
Instant “{text}” is not ISO-8601. |
TopicError
Section titled “TopicError”Thrown as TopicException.
| Reason | Message | What to do |
|---|---|---|
PayloadInvalid |
Topic payload at {path} is {actualType}; expected null, a finite scalar or an array of those. | |
PublishFailed |
Could not publish to topic {topic}: {cause} |
TriggerError
Section titled “TriggerError”Thrown as TriggerException.
| Reason | Message | What to do |
|---|---|---|
ConfigInvalid |
A Home Assistant trigger must be a map with a non-empty “trigger” or “platform” key. | |
ConfigUnencodable |
Trigger value at {path} cannot be encoded as JSON. | |
ListEmpty |
A trigger subscription needs at least one trigger. | |
TimeInvalid |
Trigger time “{time}” is neither HH:MM[:SS] nor an input_datetime or sensor entity. | |
TimePatternEmpty |
A time pattern trigger needs hours, minutes or seconds. | |
TemplateEmpty |
A template trigger needs a non-empty template. | |
ZoneInvalid |
Trigger zone “{zone}” is not a zone entity. | |
VariablesNotMap |
Trigger variables must be a map keyed by variable name. |
WaitError
Section titled “WaitError”Thrown as WaitException.
| Reason | Message | What to do |
|---|---|---|
Abandoned |
The wait ended before an event arrived, because its stream completed or its app stopped. |
MqttClientError
Section titled “MqttClientError”Thrown as MqttClientException.
| Reason | Message | What to do |
|---|---|---|
ConnectionRefused |
The MQTT server refused the connection: {refusal}. | |
ConnectionLost |
The MQTT connection was lost: {cause} | |
ConnectionClosed |
The MQTT connection is closed. | |
KeepaliveTimedOut |
The MQTT server left a keepalive ping unanswered for {keepalive}. | |
PacketMalformed |
The MQTT server sent a malformed {packetType} packet. | |
PacketUnexpected |
The MQTT server sent an unexpected {packetType} packet. | |
PacketIdsExhausted |
Every MQTT packet identifier is in flight. |
AppError
Section titled “AppError”Thrown as AppException.
| Reason | Message | What to do |
|---|---|---|
DuplicateId |
Automations {firstClass} and {secondClass} both use ID “{appId}”; “-” and “_” count as the same character. | |
DirectoryNotAutoloadable |
{directory} is not covered by a PSR-4 autoload rule. Add it to “autoload” in composer.json and run make install. |
|
NotAnApp |
{className} has #[Automation] but does not implement {appInterface}. | |
NotInstantiable |
{className} has #[Automation] but cannot be instantiated. | |
IdInvalid |
{className}: automation ID “{appId}” is invalid; use lowercase letters, digits, “-” and “_”, starting with a letter. | |
InitializeTimedOut |
{appId} initialize() did not return within {timeout}. Raise supervision.initialize_timeout if needed. | |
Unknown |
No automation with ID “{appId}”. | |
Disabled |
Automation “{appId}” is not loaded because apps.{appId}.enabled is false. |
BrokerError
Section titled “BrokerError”Thrown as BrokerException.
| Reason | Message | What to do |
|---|---|---|
NoWorkersStarted |
None of {workers} workers could be started. | |
WorkerSpawnTimedOut |
Worker {worker} did not connect within {timeout}. |
ConfigurationError
Section titled “ConfigurationError”Thrown as ConfigurationException.
| Reason | Message | What to do |
|---|---|---|
SchemaViolation |
{cause} | |
ConfigFileMissing |
Config file {path} not found. | |
ConfigFileUnparsable |
Could not parse {path}: {cause} | |
ConfigFileNotAMap |
{path} holds {actualType}; expected a map of settings. | |
ValueInvalid |
“{value}” is not {expected}. | |
KeyInvalid |
{path} must be {expected}. | |
KeyParseFailed |
{path} is invalid: {cause} | |
DurationTooShort |
{path} is {value}; expected at least {minimum}. | |
DurationTooLong |
{path} is {value}; expected at most {maximum}. | |
MaxDelayBelowInitialDelay |
{path} is {value}; expected at least {initialDelayPath} ({initialDelay}). | |
RestartWindowTooShort |
supervision.restart_window is {window}, but the backoff for restart_attempts ({attempts}) adds up to {backoffTotal}, so a crash-looping worker is never quarantined. | |
HomeAssistantMissing |
home_assistant is not configured. Set STEWART_HOME_ASSISTANT__URL and STEWART_HOME_ASSISTANT__TOKEN. | |
PlaceholderUnset |
{setting} refers to ${{variable}}, which is not set. Set it or add a default: ${{variable}:-value}. | |
ControlTokenMissing |
control.token is required while control.listen is set; set STEWART_CONTROL__TOKEN or set control.listen to “off”. | |
AdminTokenMissing |
http.admin.token is required while http.admin.listen is set; set STEWART_HTTP__ADMIN__TOKEN or set http.admin.listen to “off”. | |
WorkerIndexOutOfRange |
App “{appId}” is pinned to worker {worker}, but the highest worker index is {highestIndex}. | |
AppUnknown |
stewart.yaml configures app “{appId}”, but no automation has that ID. | |
AppSelectionUnknown |
–only names app “{appId}”, but no automation has that ID. | |
EnvironmentVariableUnknown |
{variable} is not a Stewart setting; run stewart config:reference for all options. |
Fix the variable name; keys are joined by two underscores. |
EnvironmentVariableTooDeep |
{variable} is deeper than the configuration; {valuePath} is a value, not a section. | |
EnvironmentVariableNamesSection |
{variable} names a section; set the keys under it, such as {variable}{separator}KEY. | |
EnvironmentValueInvalid |
{variable} is “{raw}”; expected {expected}. | |
EnvironmentValueUnparsable |
{variable} is not a valid inline list or map: {cause} | |
EnvironmentVariableConflict |
{variable} and {otherVariable} both set {valuePath}; keep one. | |
EnvironmentSecretFileUnreadable |
{variable} points to {path}, which cannot be read. | |
MqttPackageMissing |
mqtt.url is set, but the MQTT client is not installed; run composer require stewart-php/mqtt. |
ContainerError
Section titled “ContainerError”Thrown as ContainerException.
| Reason | Message | What to do |
|---|---|---|
ServiceTypeMismatch |
Service “{serviceId}” is {actualType}; expected {expectedType}. | |
BuildFailed |
Worker {workerId} failed to build its container: {cause} | |
AppInServicesFile |
services.php defines “{serviceId}” as automation {class}. Configure it in stewart.yaml instead. | |
PeerStoreWritable |
{class}::__construct() takes ${parameter} with #[PeerStore] as Store; type it ReadableStore. | |
PeerStoreAppIdInvalid |
{class}::__construct() takes ${parameter} with #[PeerStore]: {cause} | |
MessageHandlerDuplicated |
{firstHandler} and {secondHandler} both handle {messageClass}. | |
AppOptionInvalid |
Option “{option}” of app “{appId}” is “{value}”, which is not a valid {expectedType}. | |
AppOptionUnknown |
App “{appId}” sets option “{option}”, which matches no parameter of {class}::__construct(). | Rename the option in stewart.yaml to match a constructor parameter. |
AppOptionMissing |
App “{appId}” needs option “{option}”; {class}::__construct() takes ${option} with no default. | Set the option, or give the parameter a default. |
AppOptionAmbiguous |
App “{appId}” sets option “{option}”, which matches parameters {parameters} of {class}::__construct(); use the exact name. | |
AppOptionRepeated |
App “{appId}” sets ${parameter} of {class}::__construct() twice, as options “{firstOption}” and “{secondOption}”. | |
AppBuildFailed |
App “{appId}” cannot be built: {cause} | |
AppClassMissing |
App “{appId}” names class {class}, which cannot be loaded. | |
ServicesFileMissing |
Services file {path} no longer exists. |
ControlError
Section titled “ControlError”Thrown as ControlException.
| Reason | Message | What to do |
|---|---|---|
SocketInUse |
Another daemon is already listening on {address}. Stop it or change control.listen. | |
SocketUnremovable |
Could not remove the stale control socket {path}. | |
SocketDirectoryNotCreatable |
Could not create control socket directory {directory}. | |
SocketPathNotASocket |
The control socket path {path} exists and is not a socket. | |
FrameNotJson |
Frame is not JSON: {cause} | |
FrameNotAnObject |
Frame is {actualType}; expected a JSON object. | |
FrameUnencodable |
Could not encode {frameClass}: {cause} | |
FrameClassUnknown |
Unknown frame {frameClass}. | |
FrameUndecodable |
Could not decode the “{frameType}” control frame: {cause} | |
ConfigUnavailable |
Could not read control.listen and control.token (pass –address and –token): {cause} | |
ControlDisabled |
The configuration sets control.listen to off; pass –address. | |
TokenMissing |
No control token; set STEWART_CONTROL__TOKEN or pass –token. | |
ConnectionRejected |
The broker refused the connection: {reason} | |
UnexpectedGreeting |
The broker sent {frameClass} instead of a welcome. | |
ProtocolMismatch |
Broker speaks control protocol {brokerProtocol}, this client {clientProtocol}. | |
BrokerHungUp |
The broker hung up before the session ended. | |
UnexpectedFrame |
The broker sent {frameClass} instead of {expectedFrameClass}. | |
RequestTimedOut |
The broker did not answer within {timeout}. | |
RequestUnexpected |
The control session expects a request, not {frameClass}. | |
RequestFailed |
The broker refused the request: {message} | |
RequestUnanswerable |
The broker could not answer the request: {cause} |
DeployError
Section titled “DeployError”Thrown as DeployException.
| Reason | Message | What to do |
|---|---|---|
CommitIdInvalid |
“{value}” is not a commit hash. | |
CommandTimedOut |
{command} did not finish within {timeout}. | |
CommandFailed |
{command} failed with exit code {exitCode}: {detail} | |
PrepareOutputUnexpected |
release-prepare printed “{output}” instead of a commit and its state. |
TransportError
Section titled “TransportError”Thrown as TransportException.
| Reason | Message | What to do |
|---|---|---|
Closed |
Transport is closed. | |
SendFailed |
Failed to send to peer: {cause} | |
Unencodable |
Could not encode {messageClass} for IPC: {cause} | |
PeerDisconnected |
Peer disconnected: {cause} | |
ReceiveFailed |
Failed to receive from peer: {cause} | |
UnexpectedMessage |
Expected a JSON frame, got {actualType}. | |
UndecodableFrame |
Could not decode “{messageType}” frame: {reason} | |
ProtocolMismatch |
Broker speaks IPC protocol {actual}, this worker {expected}. | |
MessageTypeMissing |
No IPC message type is registered for {messageClass}. | |
MessageTagMissing |
IPC message {messageClass} has no #[IpcMessage] attribute. | |
MessageTagDuplicated |
IPC tag “{tag}” is declared by both {firstClass} and {secondClass}. |
WorkerError
Section titled “WorkerError”Thrown as WorkerException.
| Reason | Message | What to do |
|---|---|---|
Unknown |
No worker “{workerId}”; stewart status lists the workers. |
|
ShuttingDown |
Stewart is shutting down, so worker {workerId} is not restarted. |
StoreSetupError
Section titled “StoreSetupError”Thrown as StoreSetupException.
| Reason | Message | What to do |
|---|---|---|
DsnInvalid |
Store URL is invalid ({length} characters); expected a URL like “redis://host:6379/0”. | |
SchemeUnsupported |
No installed store backend handles “{scheme}://”; installed schemes: {supported}. | |
UnknownPeerApp |
No automation has ID “{appId}”, so its store cannot be opened. | |
PrefixInvalid |
Store prefix “{prefix}” is invalid; expected a letter or digit, then up to 63 letters, digits, “.”, “_” or “-”. |
JsonShapeError
Section titled “JsonShapeError”Thrown as JsonShapeException.
| Reason | Message | What to do |
|---|---|---|
WrongType |
“{key}” is {actualType}; expected {expected}. | |
Missing |
“{key}” is missing; expected {expected}. | |
UnexpectedValue |
“{key}” is {value}; expected {expected}. | |
InstantInvalid |
“{key}” is invalid: {cause} | |
ObjectRefused |
“{key}” was refused: {cause} |
HarnessError
Section titled “HarnessError”Thrown as HarnessException.
| Reason | Message | What to do |
|---|---|---|
AlreadyStarted |
The harness already runs {class}; use one harness per app. | |
AutomationMissing |
{class} has no #[Automation] attribute, so it is not an app. | |
NotStarted |
No app is running; call startApp() first. | |
OptionMissing |
{class}::__construct() needs option “{option}”; pass it to startApp(). | |
OptionUnknown |
Option “{option}” matches no parameter of {class}::__construct(). | |
ParameterUnresolvable |
{class}::__construct() takes ${parameter} as {type}, which the harness has no fake for; pass one with provideService(). |