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

  1. Start the service first: 'await hardware_service.start()' (and initialize() if required) before any manual collection.
  2. Guard call sites with 'if not hardware_service.is_running: await hardware_service.start()'.
  3. Reject manual-collection requests at the API layer with 409/503 when the service is not running, instead of letting RuntimeError escape.
  4. 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

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


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/0c8b374d28e14930. Report an issue: GitHub.