ruvnet/RuView · error · RuntimeError
Hardware service is not running
Error message
Hardware service is not running
What it means
trigger_manual_collection() refuses to run when the service's is_running flag is False, raising RuntimeError. The flag is set by start() and cleared by stop()/shutdown, so this error means the manual-collection API was called on a service that was never started or has already been stopped (for example after reset() or an error path). It is a lifecycle-ordering error, not a hardware failure.
Source
Thrown at archive/v1/src/services/hardware_service.py:419
"""Reset service state."""
self.stats = {
"total_samples": 0,
"successful_samples": 0,
"failed_samples": 0,
"average_sample_rate": 0.0,
"last_sample_time": None,
"connected_routers": len(self.router_interfaces)
}
self.recent_samples.clear()
self.last_error = None
self.logger.info("Hardware service reset")
async def trigger_manual_collection(self, router_id: Optional[str] = None) -> Dict[str, Any]:
"""Manually trigger data collection."""
if not self.is_running:
raise RuntimeError("Hardware service is not running")
results = {}
if router_id:
# Collect from specific router
if router_id not in self.router_interfaces:
raise ValueError(f"Router {router_id} not found")
interface = self.router_interfaces[router_id]
try:
csi_data = await interface.get_csi_data()
if csi_data is not None:
await self._process_collected_data(router_id, csi_data)
results[router_id] = {"success": True, "data_shape": csi_data.shape if hasattr(csi_data, 'shape') else None}
else:
results[router_id] = {"success": False, "error": "No data received"}
except Exception as e:
results[router_id] = {"success": False, "error": str(e)}View on GitHub (pinned to 4685618388)
Solutions
- Start the service first: 'await hardware_service.start()' (and initialize() if required) before any manual collection.
- Guard call sites with 'if not hardware_service.is_running: await hardware_service.start()'.
- Reject manual-collection requests at the API layer with 409/503 when the service is not running, instead of letting RuntimeError escape.
- If the service stopped unexpectedly, inspect last_error and restart it.
Example fix
# before
result = await hardware_service.trigger_manual_collection() # RuntimeError
# after
if not hardware_service.is_running:
await hardware_service.start()
result = await hardware_service.trigger_manual_collection() Defensive patterns
Strategy: validation
Validate before calling
if not hardware_service.is_running:
await hardware_service.start()
result = await hardware_service.trigger_manual_collection() Prevention
- Start the HardwareService during app startup (lifespan hook) so manual collection is always valid afterwards.
- Return HTTP 503 from manual-collection endpoints when is_running is False instead of letting RuntimeError escape.
- In tests, use fixtures that start the service before any collection call.
When it happens
Trigger: Calling trigger_manual_collection() before await start(); calling it after stop() or shutdown(); calling after reset() cleared state without a subsequent start; background loop crashed and flipped the running state off.
Common situations: FastAPI/health endpoints that trigger collection on demand while the service lifecycle is managed elsewhere; test code that forgets the fixture ordering; race between shutdown and an in-flight request.
Related errors
- Router {router_id} not found
- Pose service is not running
- Service not found: {service_name}
- MCP specification rejected by ${kernel.resolvedBackend} kern
- Empty data received
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/0c8b374d28e14930.
Report an issue: GitHub.